JIT
creating a detailed flutter frontend for attendEase app
AttendEase UI/UX Design Specification This document describes the complete frontend design of the AttendEase Android attendance-management app, covering all screens, navigation, interactions, and styling. It is written in clear, user-friendly language with example on-screen text, so a developer or UI designer can implement the app exactly as described. App Flow The app flow outlines how users move through the app from launch to actions. Each screen transition is triggered by a tap or swipe, using intuitive animations (typically a slide or fade). The following steps describe the flow for all user roles: Launch & Authentication: On app launch, show a splash/logo screen (full-screen app logo JPG). Then navigate (slide) to the Login Screen. From here, users can tap Sign Up to go to the registration form or Forgot Password to recover credentials. Registration: On Sign Up, the user fills in name, email, etc., and taps Register. A loading indicator appears, then a success modal (“Registration successful”) shows. The app then slides back to the Login Screen. Login & Role Landing: After a successful login, the app checks the user’s role. It then slides to the appropriate main page: Student: navigates to the Student Home tab (🏠 Home). Lecturer: navigates to the Lecturer Dashboard (🏠 Dashboard). Admin: navigates to the Admin Dashboard (🏠 Dashboard). Student Flow: From Home, the student can tap bottom-nav tabs (Courses, Profile). Tapping a course in Courses slides to a Course Details screen. If an attendance session is active, a green Check In button appears. Tapping it opens the camera (screen transition: full-screen camera view, no animation or a quick fade) for facial scanning. After check-in, a success message appears, and the student returns (pop) to the Home/Courses screen. Lecturer Flow: From Dashboard, the lecturer can tap bottom-nav tabs (Courses, Reports, Profile). In Courses, tapping a course opens its Course Details or Session History screen. A Start Session button (🕒) is visible on each course card. Tapping it slides up the Create Session form (modal overlay or new screen) where the lecturer sets location/radius. After starting, slide back with success message. On Reports, the lecturer can view attendance stats by course/date. Admin Flow: From Dashboard, the admin taps bottom-nav tabs (Students, Lecturers, Reports, Settings). In Students, tapping + Add Student slides up a New Student form (modal or full screen) to register a student. Similarly, Lecturers has + Add Lecturer. Reports shows system-wide stats. Settings has toggles (e.g. enable/disable features). All forms show a slide or fade transition and return to the list with a success modal on completion. Across the app, transitions use slide animations for navigating forward/back between screens, and fade or scale modals for dialogs. Back navigation (Android back button) slides screens back. Swiping down on lists (e.g. courses list) triggers a pull-to-refresh (native behavior, no navigation). Screen-by-Screen Breakdown Each screen has a consistent header layout: a logo placeholder (JPG, ~40×40px) at the top-left, a centered title, and optional action icons on the top-right (e.g. notifications 🔔 or logout 🚪). Below the header is the main content area. All screens use Material Design styling with standard 16dp padding. Buttons have at least 44×44px tappable area. Forms use clear labels and placeholders. Success and error messages appear as modals or snackbars. Authentication Screens Login Screen: Header: Title “Login” centered. Logo: App logo top-left. Form: Two fields: Email (placeholder: “you@example.com”, type=email, required) and Password (placeholder: “••••••••”, type=password, required, eye-icon to toggle visibility). Buttons: A primary Login button (blue background, white text, icon: 🔒 or key) below the form. Under it, a text link Forgot Password? in smaller font (tapping slides to Reset Password screen). At the bottom, a secondary link “Don’t have an account? Sign Up” which slides to the Sign Up screen. Error Handling: If login fails, show a red-bordered alert modal with icon ⚠️ and message (e.g. “Invalid email or password”). Success: On success, “Welcome!” briefly shows as a snackbar or toast, then transition. Sign Up Screen: Header: Title “Sign Up” with logo left. Form: Fields: Full Name (text, placeholder “Jane Doe”, required), Email (type=email, required), Password (type=password, placeholder “••••••••”, required), Confirm Password (type=password, required). Each field shows an icon (e.g. ✏️ for name, 📧 for email, 🔒 for password). Buttons: A primary Register button (green or blue). After the form, small text: “Already have an account? Log In” to go back. Validation: Inline hints if fields are invalid (e.g. “Enter a valid email”). On Submit: Tapping Register validates inputs. If valid, show loading spinner overlay then a modal: “Account created successfully! Please log in.” Tapping OK slides back to Login. If error (e.g. email taken), show error modal (“Email already in use.”). Forgot Password Screen (if applicable): Header: Title “Reset Password”. Form: Field for Email (placeholder “Enter your email”) and a Reset button. Behavior: On submit, show modal “Password reset link sent!” or error if email not found. Student Screens Students see a 3-tab bottom nav: 🏠 Home, 📚 Courses, 👤 Profile. Home (🏠): Header: Title “Home”, logo left. Content: A greeting text: “Welcome, [Name]!” below the header (e.g. “Welcome, Sarah!”). Below the greeting, show upcoming attendance sessions for today as a vertical list of cards. Each Session Card shows: Course name, time (icon 🕒 + time), and a status badge (🟢 Active or 🔴 Ended) on the right. If an active session is present, the card has a green Check In button (📷 icon + “Check In”) at bottom. If inactive, show grey text “Session ended”. Buttons: The Check In button (green, with white 📷 icon) spans most of the card width. Tapping it triggers the camera. Interaction: If no active sessions, display “No sessions right now. Enjoy your day!” message. Tapping Check In slides to the Camera Check-In screen (full-screen camera). Success: After check-in, show a modal “Attendance recorded!” with a ✅ icon, then refresh this list. Courses (📚): Header: Title “My Courses”. Content: A scrollable list of Course Cards (flat grey background, rounded corners). Each card shows: Course title in bold, course code (small text), and an icon or badge if needed. On the right of the card title, a small badge indicates course status: 🟢 “Active” or 🔴 “Completed”. Buttons: If there are no courses yet, show a central “Add Course” button (blue + Add Course, icon ➕). Tapping it opens an Enroll Course form (screen/modal) where student enters a course code to join a course. Interaction: Tapping a course card slides to Course Details screen. Course Details (for Student): Header: Title is the course name. Content: Show list of all attendance sessions for this course (each as a card like on Home, showing date, status, maybe attendance marked). At top, show “Attendance Progress: X/Y sessions checked in.” Buttons: If a session is currently active, show a Check In button on that session’s card as in Home. Otherwise, no action button. Profile (👤): Header: Title “Profile”. Content: Shows user info: profile picture (placeholder icon or avatar), full name, email, and student ID if any. Buttons: An Edit Profile button (✏️) to modify info, and a Log Out button at the bottom (icon 🚪). Tapping Edit slides to an Edit Profile form with fields (Name, Email, maybe phone), with Save/Cancel buttons. Tapping Log Out shows a confirmation modal (“Are you sure? You will be logged out.”) with Yes/No buttons; confirming logs out and returns to Login. Lecturer Screens Lecturers see a 4-tab bottom nav: 🏠 Dashboard, 📚 Courses, 📊 Reports, 👤 Profile. Dashboard (🏠): Header: Title “Dashboard”. Content: A summary of the day’s activities. For example, a card saying “You have 2 upcoming sessions today.” and buttons to Start Session for the next course. Show the Start Session action as a prominent button (green 🕒 icon) or a card listing today’s courses with “Start” buttons. Buttons: Start Session (with icon 🕒) opens the Create Session screen for a selected course. Courses (📚): Header: Title “My Courses”. Content: List of Course Cards, similar to Student’s, showing course name and code. Under each, show number of enrolled students or “Sessions this month”. Buttons: Each card has a Start Session button (🕒, green) on the right. If a session is active, that card’s status badge shows 🟢 “Active Session”. If none active, grey Start. Add Course: At top or bottom, an Add Course button (blue ➕ icon). Tapping slides to a New Course form (fields: Course Title, Code, Description) with Create button. Create Session Screen: Header: Title “New Session”. Form: Fields: Session Name (text), Location (sub-label “Use current location” with a button 📍 that captures GPS), Radius slider (e.g. 0–100m), and optionally Start Time (default now, type=time). Buttons: Start (green) and Cancel (text button). Behavior: Tapping Start validates fields (must have location). If missing location, show error modal (“Please set a location.”). On success, send data; show modal “Session started successfully!”; then return (slide back) to Courses screen. The new session becomes active for students. Reports (📊): Header: Title “Reports”. Content: A dropdown or tabs to select course or date range. Below, display a chart or summary stats (e.g. total sessions, average attendance). Include a list of recent sessions: each with date, session name, and attendance percentage. Use simple bars or pie charts. Buttons: Export (sheet icon 📄) to download CSV, and Filter (funnel icon 🔍). Profile (👤): Similar to Student Profile, but include Lecturer ID. Buttons: Edit Profile (✏️) and Log Out (🚪) with similar behavior. Admin Screens Admins see a 5-tab bottom nav: 🏠 Dashboard, 👥 Students, 👨‍🏫 Lecturers, 📊 Reports, ⚙️ Settings. Dashboard (🏠): Header: Title “Dashboard”. Content: Key stats cards (e.g. Total Students, Total Lecturers, Active Sessions). Each card shows an icon and number. For example, a green card: “🟢 Active Sessions: 3”, a blue card: “👥 Students: 120”. Buttons: Possibly Refresh (circular arrow icon 🔄) to update stats. Students (👥): Header: Title “Students”. Content: A list of Student Cards. Each card shows student name, email, and status badge (🟢 Active or 🔴 Inactive) in the corner. Buttons: At top or bottom, a + Add Student button (blue ➕) that opens a New Student form (fields: Name, Email, ID, Password, etc.) with Create. Each card has an Edit (✏️) icon and a Delete (🗑️) icon on the right. Tapping Edit opens a similar form to update details. Tapping Delete shows a confirmation modal “Delete this student?” with Yes/No. Lecturers (👨‍🏫): Header: Title “Lecturers”. Content: Similar list of Lecturer Cards (name, email, status badge). Buttons: + Add Lecturer (blue ➕) to open New Lecturer form. Each card has Edit and Delete actions. Reports (📊): Header: Title “Reports”. Content: System-wide attendance statistics. For example, a table or list of courses with average attendance, or a time-series chart of daily check-ins. Provide options to filter by date range or export data. Buttons: Export All (icon 📈) to download summary, and filters. Settings (⚙️): Header: Title “Settings”. Content: Toggles and options: e.g. Dark Mode switch, Attendance Radius Default slider, Session Timeout selector. Buttons: A Save Settings button (primary) at bottom. Also include Log Out (🚪) here as a menu item. Behavior: Changing a setting immediately applies or shows a “Settings saved” snackbar. Each form uses placeholders and hints (e.g. “Enter course name”). Required fields are marked with an asterisk or bold label. Validation errors appear below fields in red text (e.g. “This field is required”). Success messages use modals (centered box with a checkmark). Navigation The app uses a bottom tab bar for primary navigation, with a fixed icon + label for each tab. The order and appearance per role: Student (3 tabs): 🏠 Home – opens Home screen. 📚 Courses – opens Courses list. 👤 Profile – opens Profile. Appearance: Icons (24px) with labels underneath. On small screens, labels may truncate (e.g. “Home” as “Ho…”) or hide entirely, but tooltips appear on long press. The tabs remain equally spaced. When active, icon and label use the primary color (blue), inactive tabs are grey (#757575). Lecturer (4 tabs): 🏠 Dashboard 📚 Courses 📊 Reports 👤 Profile Small screen behavior: All four fit on phones; labels use smaller font if needed. Active tab is highlighted (blue icon and text). Admin (5 tabs): 🏠 Dashboard 👥 Students 👨‍🏫 Lecturers 📊 Reports ⚙️ Settings Small screen: Five icons still fit, but labels may use shorter text or a smaller font. If absolutely necessary, the bar can scroll horizontally (though uncommon). Touch areas remain ≥44px. The active tab icon is green (match Dashboard card color) or the theme color. In all cases, the bottom nav is fixed at the screen bottom and visible on every main page. Tapping a tab causes a slide or fade transition to that section. If already on the tab, tapping it may scroll content to top. Icons Used We use clear icons (emoji shown here as examples, but in implementation use Material icons or assets): Home/Dashboard: 🏠 (used on top-left of nav and on Dashboard cards) Courses: 📚 (used for Courses tab and on course cards) Profile: 👤 (user icon, Profile tab) Reports: 📊 (bar chart icon, Reports tab) Students (admin): 👥 (two-person icon, Students tab) Lecturers (admin): 👨‍🏫 (teacher icon, Lecturers tab) Settings: ⚙️ (gear icon, Settings tab and header) Add/New: ➕ (plus icon, for adding courses/students) Edit: ✏️ (pencil icon, for editing profile or items) Delete: 🗑️ (trash icon) Check In / Camera: 📷 (camera icon on Check In button) Start Session / Time: 🕒 (clock icon on Start Session button) Location: 📍 (pin icon next to “Use current location” button) Logout: 🚪 (door or power icon for Log Out) Refresh: 🔄 (circular arrow for refreshing data) Information/Help: ❓ or ℹ️ (if needed on profile or settings) Icons are placed consistently: on buttons (left side of label), on card badges, and on nav bar. All icons are at least 24px and use the app’s accent color. For example, the Check In button shows 📷 on its left and reads “Check In”. The Start Session button shows 🕒 and “Start Session”. Color Scheme We use a clean, high-contrast palette with a vibrant accent. Two modes (light/dark) are supported. Primary Color: #1976D2 (a vivid blue) – used for headers, primary buttons, active icons/text. Secondary/Accent: #4CAF50 (green) – used for success actions (e.g. Check In, Start Session) and status (🟢 badges). Light Background: #FFFFFF (white) for screens, #F5F5F5 for cards. Dark Background: #121212 for app background, #1E1E1E for cards (in dark mode). Text (Light): #212121 (primary text), #757575 (secondary text). Text (Dark): #FFFFFF (primary), #B0BEC5 (secondary). Error/Alerts: #D32F2F (red) – for errors and 🔴 badges. Progress Bars/Charts: #1976D2 (blue) and #4CAF50 (green). Status Badges: 🟢 Active (#4CAF50 background with white text) and 🔴 Inactive (#F44336 red with white text). Gradients: On the Login/SignUp screens, use a gentle top-to-bottom gradient from #42A5F5 (light blue) to #1E88E5 (darker blue) as background. Primary buttons can have a subtle gradient as well (e.g. blue to lighter blue). Borders/Dividers: #E0E0E0 (light mode) or #424242 (dark mode) as thin lines. Snackbars/Toasts: Background #323232 (dark grey), text #FFFFFF. All colors respect accessibility (sufficient contrast). Use Flutter’s ThemeData to define light and dark themes with these hex values. Buttons and icons use the theme accent. For example, the Login button background is #1976D2 with text #FFFFFF, and on hover it uses a slightly darker shade (#1565C0). The Check In button is #4CAF50. Responsive Layout The app is fully responsive to different mobile screen sizes and orientations: Flex Layout: Use Column, Row, and Expanded/Flexible widgets to build layouts that stretch or wrap. For example, the bottom navigation always stays at the bottom with fixed height, content above uses ListView or Column to scroll. SafeArea: Content is wrapped in a SafeArea to avoid notches and system UI overlaps. Font Scaling: Text sizes use sp units (in Flutter, normal Text) so they scale with device settings. Headings are around 20sp, body text ~16sp. On very small screens, text may reduce by one size to fit without clipping. Grid/Lists: The Courses and Students/Lecturers screens use a ListView (single column). If in landscape or on tablets, these could adapt to a 2-column GridView of cards, but not required for the MVP. Images/Icons: All icons/buttons maintain a touch area ≥44×44px. For example, even if an icon appears 24px, it has 10px padding around it. Bottom Nav: Always visible and fixed. On very narrow phones, the tab labels use smaller font or hide under the icons (Flutter’s BottomNavigationBar automatically handles this in shifting mode for more tabs). Orientation: In portrait, use single-column layouts. In landscape, the layout may show more items per row (e.g., 2 course cards side by side) if space allows. Ensure scroll if content overflows. Spacing: Use consistent 16dp padding between elements. For small screens, this padding remains the same to keep touch targets comfortable. Avoid horizontal scrolling. Touch Targets: Buttons and list items have at least 44dp height. For example, each bottom nav item is at least 56dp tall for easy tapping. Animations: Smooth fade/slide animations adapt to the device framerate (standard Material transitions). Avoid janky or overly long animations on slower devices. Interaction & Data Flow Logic The app’s screens send and receive data via a backend API. Here is how key interactions and data flows work: Sign Up: User enters name, email, password and taps Register. The app sends a POST request to /api/register with JSON {name, email, password}. While waiting, a loading spinner overlays the form. On success (HTTP 201), a modal “Account created” appears and the user is returned to Login. If the server returns an error (e.g. email exists), an alert “Email already used” is shown. Login: User enters email/password and taps Login. A POST to /api/login is sent. On success, the response includes a user object (name, role, token). The app saves the token (secure storage) and loads user info into state. Then it navigates to the appropriate main screen. If login fails, show an error modal. Starting a Session (Lecturer): In the Create Session form, after choosing a course, the lecturer taps Start. The app requests the device’s location permission. Once location is fetched, the app collects the form data (session name, coordinates, radius) and sends it via POST /api/sessions (with token auth). On success, the session is marked active. The UI shows a “Session started” toast, and the Courses list refreshes to show an active session badge on that course. All students in that course receive a push notification (or will see the session on next refresh). Student Check-In: When the student taps Check In, the app does the following: Location Check: It first requests location permission (if not already allowed). It gets current GPS coordinates and checks distance to the session location. If distance > radius, it shows an error modal “You are not within the session area.” and aborts. Face Capture: The app opens the camera (preferring front camera). On the Camera Check-In screen, it shows a circular overlay and the text “Align your face and tap the camera button”. The student taps a camera icon to capture a photo. (Alternatively, auto-capture when face is detected.) Face Recognition: The captured image is sent to a face-recognition service. This could be done on-device (e.g. using a ML kit) or via the backend. The app sends a POST /api/checkin with {sessionId, studentId, imageData, location}. Server Validation: The server verifies the face matches the student’s registered face and that the location is within range. On success, it records the attendance. It then responds with success. Feedback: If successful, the app displays a success modal (“Check-in successful!”) and a ✅ icon. The student is then returned to the Home screen, where the session card now shows “Checked In” or becomes inactive. If recognition fails, show an error modal (“Face not recognized. Try again.”) and allow another attempt. Data Updates: When a check-in occurs, the lecturer’s Courses and Dashboard automatically update (next time they view) to increase the attendance count for that session. Similarly, Reports pages update with the new data. This can be done by refreshing data on screen focus or via real-time sockets. Profile Edit: When user edits profile details and taps Save, the app sends a PUT request to /api/users/{id}. On success, the profile screen updates with new info and shows a snackbar “Profile updated.” Logout: When the user taps Log Out, the app clears the stored token and navigates back to Login. Example Data Flow: Student “Alice” logs in. (POST /login → token) Alice sees active session for Math 101. She taps “Check In”. App gets GPS (Alice is on campus, within 50m). App opens camera and snaps Alice’s face. POST /checkin with {sessionId:123, studentId:456, image:..., location:...}. Server verifies Alice’s face, records attendance. Returns success. App shows “Attendance recorded!” Then Alice’s Home refreshes. All network errors (timeout, no internet) show a banner: “Unable to connect. Check your internet.” Beginner-Friendly Language & Text This guide uses clear, simple language and realistic on-screen wording. For example, screens greet the user by name: “Welcome, Sarah!” on the Home screen. Buttons use action words: “Start Session”, “Check In”, “Add Student”. Instructional text guides the user, e.g. “Align your face with the circle and tap 📷 to check in.” or “Tap the green button to check in”. Error messages are polite: “Oops! Something went wrong, please try again.” All interactive elements are described in imperative mood (e.g. “Tap Edit Profile to change your details.”). Inline examples of on-screen text: Home greeting: Welcome, Alice! Check In prompt: “Tap the green Check In button to mark your attendance.” Success popup: “✅ Attendance recorded!” Session status badge: 🟢 Active or 🔴 Ended (with those colored dots). Empty state message: “No courses yet. Tap + Add Course to get started.” Logout confirmation: “Are you sure you want to log out?” The wording remains consistent (e.g. always “Session” not “Meet”, always “Course” not “Class”). All labels and instructions follow this style so even first-time users understand what to do. For example, on the New Course screen, the save button reads “Create Course”, and tapping it shows “Course created successfully!” in a friendly modal. Suggested Flutter File Structure A clean, modular Flutter project structure helps manage the Student, Lecturer, and Admin sections. A recommended structure in lib/ might be: lib/main.dart – App entry point; sets up MaterialApp, themes, and routes. lib/auth/ – Authentication logic and screens. login_screen.dart, signup_screen.dart, reset_password.dart – UI for auth. auth_service.dart – Methods for login, signup, token management. lib/models/ – Data models (e.g. user.dart, course.dart, session.dart, attendance.dart). Each class maps to the backend JSON. lib/services/ – Core services. api_service.dart – HTTP requests (login, fetch courses, check-in, etc.). location_service.dart – Wrapper for GPS access. face_recognition_service.dart – (if on-device) or communication with face API. lib/widgets/ – Reusable widgets (e.g. course_card.dart, session_card.dart, status_badge.dart, custom_button.dart). These are shared UI components used across screens. lib/screens/ – Screen folder with subfolders per role/section. screens/student/ – Student-specific pages (e.g. home.dart, courses.dart, course_detail.dart, profile.dart, checkin_camera.dart). screens/lecturer/ – Lecturer pages (dashboard.dart, courses.dart, new_course.dart, new_session.dart, reports.dart, profile.dart). screens/admin/ – Admin pages (dashboard.dart, students_list.dart, new_student.dart, lecturers_list.dart, reports.dart, settings.dart). screens/shared/ – Pages used by multiple roles (if any, e.g. profile.dart if identical, or error pages). lib/utils/ – Helper utilities (e.g. validators.dart, constants.dart for color hex codes, navigation.dart for route names). assets/ (not in lib) – logo.jpg – Placeholder logo image (as mentioned for header). Icons and any necessary images. Each folder’s purpose: auth/ – Handles all login/signup operations and screens. models/ – Defines the data structures for Users, Courses, Sessions, etc., used by the UI. services/ – Contains code that talks to APIs or device features (network, GPS, camera). widgets/ – Common UI parts to avoid duplication (e.g. a standardized AppBar with logo). screens/ – All screen widgets organized by feature or user role for clarity. utils/ – Non-UI utilities (form validators, constants, global theme). assets/ – Static assets like images (the logo placeholder JPG goes here). This structure keeps code organized by functionality and user role. For example, all Student pages are in one folder, making it easy to find and maintain. Shared resources (colors, icons, widgets) are centralized so different parts of the app look consistent. By following this file structure, developers can quickly locate and edit the specific screens or services they need. base on this description provide the flutter frontend implementation only.use the exact same icons in this description. please the code should have the correct syntax, should no thave dependencies that depend on developer mode and should not download any artifacts. use all the icons in this description exactly as they are whether as emoji or svg because i don't have any images of these icons so i'm depending on you . follow the file structure provided in the description. the code for all files under each folder should be provided. please provide the code following the order of the file structure.. add the flutter dependency file.please the code should not depend on devoloper mode and download of artifacts. it should not need network connection to run. it please use valid and correct dart syntax. please make sure that the code uses the exact icons found in the design specification and that the dart code follows the dart syntax for the latest version of dart. please the add the dependencies should not be outdated, depend on developer mode nor online artifact. it should work without network as this is just the frontend implementation. use mockup data to test that the frontend is interactive and responsive. please provide code for all the files in the file structure. please don't forget to add the code for the home page 4 slide shows(i have the images).please follow the file structure in order and provide the code for each code. now lets code

I'll provide the complete Flutter frontend implementation following the exact specifications and file structure you provided. Here's the comprehensive solution:

<jitProject id="attendease-app" title="AttendEase Attendance Management App"> <jitFile type="file" filePath="pubspec.yaml"> name: attendease description: Attendance management application for students, lecturers and admins.

version: 1.0.0+1

environment: sdk: '>=3.0.0 <4.0.0'

dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.5 flutter_svg: ^2.0.7 camera: ^0.10.5 geolocator: ^10.1.0 provider: ^6.0.5 intl: ^0.18.1 flutter_secure_storage: ^8.0.0

dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^2.0.0

flutter: uses-material-design: true assets: - assets/ </jitFile>

<jitFile type="file" filePath="lib/main.dart"> import 'package:flutter/material.dart'; import 'package:attendease/auth/auth_service.dart'; import 'package:attendease/screens/shared/splash_screen.dart'; import 'package:provider/provider.dart';

void main() { runApp( ChangeNotifierProvider( create: (context) => AuthService(), child: const MyApp(), ), ); }

class MyApp extends StatelessWidget { const MyApp({super.key});

@override Widget build(BuildContext context) { return MaterialApp( title: 'AttendEase', debugShowCheckedModeBanner: false, theme: ThemeData( colorScheme: ColorScheme.light( primary: const Color(0xFF1976D2), secondary: const Color(0xFF4CAF50), surface: Colors.white, background: const Color(0xFFF5F5F5), error: const Color(0xFFD32F2F), onPrimary: Colors.white, onSecondary: Colors.white, onSurface: const Color(0xFF212121), onBackground: const Color(0xFF212121), onError: Colors.white, brightness: Brightness.light, ), useMaterial3: true, ), darkTheme: ThemeData( colorScheme: ColorScheme.dark( primary: const Color(0xFF1976D2), secondary: const Color(0xFF4CAF50), surface: const Color(0xFF1E1E1E), background: const Color(0xFF121212), error: const Color(0xFFD32F2F), onPrimary: Colors.white, onSecondary: Colors.white, onSurface: Colors.white, onBackground: Colors.white, onError: Colors.white, brightness: Brightness.dark, ), useMaterial3: true, ), home: const SplashScreen(), ); } } </jitFile>

<jitFile type="file" filePath="lib/auth/auth_service.dart"> import 'package:flutter/material.dart';

class AuthService extends ChangeNotifier { String? _token; String? _userRole; String? _userName; String? _userEmail;

String? get token => _token; String? get userRole => _userRole; String? get userName => _userName; String? get userEmail => _userEmail;

Future<void> login(String email, String password) async { // Mock login - in real app this would call an API _token = 'mock_token'; _userRole = 'student'; // Can be 'student', 'lecturer', or 'admin' _userName = 'John Doe'; _userEmail = email; notifyListeners(); }

Future<void> signUp(String name, String email, String password) async { // Mock signup _token = 'mock_token'; _userRole = 'student'; _userName = name; _userEmail = email; notifyListeners(); }

Future<void> logout() async { _token = null; _userRole = null; _userName = null; _userEmail = null; notifyListeners(); }

Future<void> resetPassword(String email) async { // Mock password reset await Future.delayed(const Duration(seconds: 1)); } } </jitFile>

<jitFile type="file" filePath="lib/auth/login_screen.dart"> import 'package:flutter/material.dart'; import 'package:attendease/auth/auth_service.dart'; import 'package:provider/provider.dart'; import 'signup_screen.dart'; import 'reset_password_screen.dart';

class LoginScreen extends StatefulWidget { const LoginScreen({super.key});

@override State<LoginScreen> createState() => _LoginScreenState(); }

class _LoginScreenState extends State<LoginScreen> { final _formKey = GlobalKey<FormState>(); final _emailController = TextEditingController(); final _passwordController = TextEditingController(); bool _isLoading = false; bool _obscurePassword = true;

Future<void> _submit() async { if (!_formKey.currentState!.validate()) return;

setState(() => _isLoading = true);
try {
  await Provider.of<AuthService>(context, listen: false).login(
    _emailController.text.trim(),
    _passwordController.text.trim(),
  );
} catch (e) {
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(
      content: Text('Login failed: ${e.toString()}'),
      backgroundColor: Theme.of(context).colorScheme.error,
    ),
  );
} finally {
  setState(() => _isLoading = false);
}

}

@override Widget build(BuildContext context) { return Scaffold( body: Container( decoration: const BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Color(0xFF42A5F5), Color(0xFF1E88E5)], ), ), child: Center( child: SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: Card( elevation: 8, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), child: Padding( padding: const EdgeInsets.all(24.0), child: Form( key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( '🏫', style: TextStyle(fontSize: 48), ), const SizedBox(height: 16), const Text( 'Login', style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 24), TextFormField( controller: _emailController, decoration: const InputDecoration( labelText: 'Email', prefixIcon: Icon(Icons.email), border: OutlineInputBorder(), ), keyboardType: TextInputType.emailAddress, validator: (value) { if (value == null || value.isEmpty) { return 'Please enter your email'; } if (!value.contains('@')) { return 'Please enter a valid email'; } return null; }, ), const SizedBox(height: 16), TextFormField( controller: _passwordController, decoration: InputDecoration( labelText: 'Password', prefixIcon: const Icon(Icons.lock), border: const OutlineInputBorder(), suffixIcon: IconButton( icon: Icon( _obscurePassword ? Icons.visibility : Icons.visibility_off, ), onPressed: () { setState(() { _obscurePassword = !_obscurePassword; }); }, ), ), obscureText: _obscurePassword, validator: (value) { if (value == null || value.isEmpty) { return 'Please enter your password'; } if (value.length < 6) { return 'Password must be at least 6 characters'; } return null; }, ), const SizedBox(height: 8), Align( alignment: Alignment.centerRight, child: TextButton( onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => const ResetPasswordScreen(), ), ); }, child: const Text('Forgot Password?'), ), ), const SizedBox(height: 16), SizedBox( width: double.infinity, height: 50, child: ElevatedButton( onPressed: _isLoading ? null : _submit, style: ElevatedButton.styleFrom( backgroundColor: Theme.of(context).colorScheme.primary, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: _isLoading ? const CircularProgressIndicator( color: Colors.white, ) : const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.login), SizedBox(width: 8), Text('Login'), ], ), ), ), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text("Don't have an account?"), TextButton( onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => const SignUpScreen(), ), ); }, child: const Text('Sign Up'), ), ], ), ], ), ), ), ), ), ), ), ); }

@override void dispose() { _emailController.dispose(); _passwordController.dispose(); super.dispose(); } } </jitFile>

<jitFile type="file" filePath="lib/auth/signup_screen.dart"> import 'package:flutter/material.dart'; import 'package:attendease/auth/auth_service.dart'; import 'package:provider/provider.dart'; import 'login_screen.dart';

class SignUpScreen extends StatefulWidget { const SignUpScreen({super.key});

@override State<SignUpScreen> createState() => _SignUpScreenState(); }

class _SignUpScreenState extends State<SignUpScreen> { final _formKey = GlobalKey<FormState>(); final _nameController = TextEditingController(); final _emailController = TextEditingController(); final _passwordController = TextEditingController(); final _confirmPasswordController = TextEditingController(); bool _isLoading = false; bool _obscurePassword = true; bool _obscureConfirmPassword = true;

Future<void> _submit() async { if (!_formKey.currentState!.validate()) return; if (_passwordController.text != _confirmPasswordController.text) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Passwords do not match'), backgroundColor: Colors.red, ), ); return; }

setState(() => _isLoading = true);
try {
  await Provider.of<AuthService>(context, listen: false).signUp(
    _nameController.text.trim(),
    _emailController.text.trim(),
    _passwordController.text.trim(),
  );

  // Show success dialog
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('Registration Successful'),
      content: const Text('Your account has been created successfully.'),
      actions: [
        TextButton(
          onPressed: () {
            Navigator.pop(context);
            Navigator.pushReplacement(
              context,
              MaterialPageRoute(
                builder: (context) => const LoginScreen(),
              ),
            );
          },
          child: const Text('OK'),
        ),
      ],
    ),
  );
} catch (e) {
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(
      content: Text('Registration failed: ${e.toString()}'),
      backgroundColor: Theme.of(context).colorScheme.error,
    ),
  );
} finally {
  setState(() => _isLoading = false);
}

}

@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Sign Up'), ), body: SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: Form( key: _formKey, child: Column( children: [ const SizedBox(height: 16), const Text( '🏫', style: TextStyle(fontSize: 48), ), const SizedBox(height: 16), const Text( 'Create Account', style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 24), TextFormField( controller: _nameController, decoration: const InputDecoration( labelText: 'Full Name', prefixIcon: Icon(Icons.person), border: OutlineInputBorder(), ), validator: (value) { if (value == null || value.isEmpty) { return 'Please enter your name'; } return null; }, ), const SizedBox(height: 16), TextFormField( controller: _emailController, decoration: const InputDecoration( labelText: 'Email', prefixIcon: Icon(Icons.email), border: OutlineInputBorder(), ), keyboardType: TextInputType.emailAddress, validator: (value) { if (value == null || value.isEmpty) { return 'Please enter your email'; } if (!value.contains('@')) { return 'Please enter a valid email'; } return null; }, ), const SizedBox(height: 16), TextFormField( controller: _passwordController, decoration: InputDecoration( labelText: 'Password', prefixIcon: const Icon(Icons.lock), border: const OutlineInputBorder(), suffixIcon: IconButton( icon: Icon( _obscurePassword ? Icons.visibility : Icons.visibility_off, ), onPressed: () { setState(() { _obscurePassword = !_obscurePassword; }); }, ), ), obscureText: _obscurePassword, validator: (value) { if (value == null || value.isEmpty) { return 'Please enter a password'; } if (value.length < 6) { return 'Password must be at least 6 characters'; } return null; }, ), const SizedBox(height: 16), TextFormField( controller: _confirmPasswordController, decoration: InputDecoration( labelText: 'Confirm Password', prefixIcon: const Icon(Icons.lock), border: const OutlineInputBorder(), suffixIcon: IconButton( icon: Icon( _obscureConfirmPassword ? Icons.visibility : Icons.visibility_off, ), onPressed: () { setState(() { _obscureConfirmPassword = !_obscureConfirmPassword; }); }, ), ), obscureText: _obscureConfirmPassword, validator: (value) { if (value == null || value.isEmpty) { return 'Please confirm your password'; } return null; }, ), const SizedBox(height: 24), SizedBox( width: double.infinity, height: 50, child: ElevatedButton( onPressed: _isLoading ? null : _submit, style: ElevatedButton.styleFrom( backgroundColor: Theme.of(context).colorScheme.primary, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: _isLoading ? const CircularProgressIndicator( color: Colors.white, ) : const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.person_add), SizedBox(width: 8), Text('Register'), ], ), ), ), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text('Already have an account?'), TextButton( onPressed: () { Navigator.pushReplacement( context, MaterialPageRoute( builder: (context) => const LoginScreen(), ), ); }, child: const Text('Log In'), ), ], ), ], ), ), ), ); }

@override void dispose() { _nameController.dispose(); _emailController.dispose(); _passwordController.dispose(); _confirmPasswordController.dispose(); super.dispose(); } } </jitFile>

<jitFile type="file" filePath="lib/auth/reset_password_screen.dart"> import 'package:flutter/material.dart';

class ResetPasswordScreen extends StatefulWidget { const ResetPasswordScreen({super.key});

@override State<ResetPasswordScreen> createState() => _ResetPasswordScreenState(); }

class _ResetPasswordScreenState extends State<ResetPasswordScreen> { final _formKey = GlobalKey<FormState>(); final _emailController = TextEditingController(); bool _isLoading = false;

Future<void> _submit() async { if (!_formKey.currentState!.validate()) return;

setState(() => _isLoading = true);
try {
  // Mock password reset
  await Future.delayed(const Duration(seconds: 2));
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('Password Reset'),
      content: const Text(
          'If an account exists with this email, a password reset link has been sent.'),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('OK'),
        ),
      ],
    ),
  );
} finally {
  setState(() => _isLoading = false);
}

}

@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Reset Password'), ), body: SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: Form( key: _formKey, child: Column( children: [ const SizedBox(height: 16), const Text( '🔑', style: TextStyle(fontSize: 48), ), const SizedBox(height: 16), const Text( 'Reset Password', style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 24), const Text( 'Enter your email address and we will send you a link to reset your password.', textAlign: TextAlign.center, ), const SizedBox(height: 24), TextFormField( controller: _emailController, decoration: const InputDecoration( labelText: 'Email', prefixIcon: Icon(Icons.email), border: OutlineInputBorder(), ), keyboardType: TextInputType.emailAddress, validator: (value) { if (value == null || value.isEmpty) { return 'Please enter your email'; } if (!value.contains('@')) { return 'Please enter a valid email'; } return null; }, ), const SizedBox(height: 24), SizedBox( width: double.infinity, height: 50, child: ElevatedButton( onPressed: _isLoading ? null : _submit, style: ElevatedButton.styleFrom( backgroundColor: Theme.of(context).colorScheme.primary, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: _isLoading ? const CircularProgressIndicator( color: Colors.white, ) : const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.send), SizedBox(width: 8), Text('Send Reset Link'), ], ), ), ), ], ), ), ), ); }

@override void dispose() { _emailController.dispose(); super.dispose(); } } </jitFile>

<jitFile type="file" filePath="lib/models/user.dart"> class User { final String id; final String name; final String email; final String role; // 'student', 'lecturer', or 'admin' final String? studentId; final String? lecturerId;

User({ required this.id, required this.name, required this.email, required this.role, this.studentId, this.lecturerId, });

factory User.fromJson(Map<String, dynamic> json) { return User( id: json['id'], name: json['name'], email: json['email'], role: json['role'], studentId: json['studentId'], lecturerId: json['lecturerId'], ); }

Map<String, dynamic> toJson() { return { 'id': id, 'name': name, 'email': email, 'role': role, 'studentId': studentId, 'lecturerId': lecturerId, }; } } </jitFile>

<jitFile type="file" filePath="lib/models/course.dart"> class Course { final String id; final String code; final String title; final String? description; final String lecturerId; final List<String> studentIds; final bool isActive;

Course({ required this.id, required this.code, required this.title, this.description, required this.lecturerId, required this.studentIds, this.isActive = true, });

factory Course.fromJson(Map<String, dynamic> json) { return Course( id: json['id'], code: json['code'], title: json['title'], description: json['description'], lecturerId: json['lecturerId'], studentIds: List<String>.from(json['studentIds']), isActive: json['isActive'] ?? true, ); }

Map<String, dynamic> toJson() { return { 'id': id, 'code': code, 'title': title, 'description': description, 'lecturerId': lecturerId, 'studentIds': studentIds, 'isActive': isActive, }; } } </jitFile>

<jitFile type="file" filePath="lib/models/session.dart"> class Session { final String id; final String courseId; final String name; final DateTime startTime; final DateTime endTime; final double latitude; final double longitude; final double radius; // in meters final bool isActive; final List<String> attendedStudentIds;

Session({ required this.id, required this.courseId, required this.name, required this.startTime, required this.endTime, required this.latitude, required this.longitude, required this.radius, this.isActive = false, this.attendedStudentIds = const [], });

factory Session.fromJson(Map<String, dynamic> json) { return Session( id: json['id'], courseId: json['courseId'], name: json['name'], startTime: DateTime.parse(json['startTime']), endTime: DateTime.parse(json['endTime']), latitude: json['latitude'], longitude: json['longitude'], radius: json['radius'], isActive: json['isActive'] ?? false, attendedStudentIds: List<String>.from(json['attendedStudentIds'] ?? []), ); }

Map<String, dynamic> toJson() { return { 'id': id, 'courseId': courseId, 'name': name, 'startTime': startTime.toIso8601String(), 'endTime': endTime.toIso8601String(), 'latitude': latitude, 'longitude': longitude, 'radius': radius, 'isActive': isActive, 'attendedStudentIds': attendedStudentIds, }; } } </jitFile>

<jitFile type="file" filePath="lib/models/attendance.dart"> class Attendance { final String id; final String sessionId; final String studentId; final DateTime timestamp; final bool verified;

Attendance({ required this.id, required this.sessionId, required this.studentId, required this.timestamp, this.verified = false, });

factory Attendance.fromJson(Map<String, dynamic> json) { return Attendance( id: json['id'], sessionId: json['sessionId'], studentId: json['studentId'], timestamp: DateTime.parse(json['timestamp']), verified: json['verified'] ?? false, ); }

Map<String, dynamic> toJson() { return { 'id': id, 'sessionId': sessionId, 'studentId': studentId, 'timestamp': timestamp.toIso8601String(), 'verified': verified, }; } } </jitFile>

<jitFile type="file" filePath="lib/services/api_service.dart"> import 'package:attendease/models/user.dart'; import 'package:attendease/models/course.dart'; import 'package:attendease/models/session.dart'; import 'package:attendease/models/attendance.dart';

class ApiService { // Mock data for demonstration final List<Course> _mockCourses = [ Course( id: '1', code: 'CS101', title: 'Introduction to Computer Science', lecturerId: 'lecturer1', studentIds: ['student1', 'student2'], ), Course( id: '2', code: 'MATH201', title: 'Advanced Mathematics', lecturerId: 'lecturer2', studentIds: ['student1'], ), ];

final List<Session> _mockSessions = [ Session( id: '1', courseId: '1', name: 'Lecture 1', startTime: DateTime.now().subtract(const Duration(hours: 1)), endTime: DateTime.now().add(const Duration(hours: 1)), latitude: 37.422, longitude: -122.084, radius: 100, isActive: true, ), Session( id: '2', courseId: '1', name: 'Lecture 2', startTime: DateTime.now().add(const Duration(days: 1)), endTime: DateTime.now().add(const Duration(days: 1, hours: 2)), latitude: 37.422, longitude: -122.084, radius: 100, isActive: false, ), ];

final List<Attendance> _mockAttendances = [ Attendance( id: '1', sessionId: '1', studentId: 'student1', timestamp: DateTime.now(), verified: true, ), ];

Future<User> getUser(String userId) async { await Future.delayed(const Duration(seconds: 1)); return User( id: userId, name: 'John Doe', email: 'john@example.com', role: 'student', studentId: 'student1', ); }

Future<List<Course>> getCoursesForStudent(String studentId) async { await Future.delayed(const Duration(seconds: 1)); return _mockCourses .where((course) => course.studentIds.contains(studentId)) .toList(); }

Future<List<Course>> getCoursesForLecturer(String lecturerId) async { await Future.delayed(const Duration(seconds: 1)); return _mockCourses .where((course) => course.lecturerId == lecturerId) .toList(); }

Future<List<Session>> getSessionsForCourse(String courseId) async { await Future.delayed(const Duration(seconds: 1)); return _mockSessions .where((session) => session.courseId == courseId) .toList(); }

Future<List<Session>> getActiveSessionsForStudent(String studentId) async { await Future.delayed(const Duration(seconds: 1)); final studentCourses = await getCoursesForStudent(studentId); // Get student's courses final courseIds = studentCourses.map((course) => course.id).toList();

return _mockSessions
    .where((session) =>
        courseIds.contains(session.courseId) && session.isActive)
    .toList();

}

Future<Attendance> checkInStudent({ required String sessionId, required String studentId, required double latitude, required double longitude, required String imageData, }) async { await Future.delayed(const Duration(seconds: 2)); final newAttendance = Attendance( id: '${_mockAttendances.length + 1}', sessionId: sessionId, studentId: studentId, timestamp: DateTime.now(), verified: true, ); _mockAttendances.add(newAttendance); return newAttendance; }

Future<Session> createSession({ required String courseId, required String name, required double latitude, required double longitude, required double radius, required int durationMinutes, }) async { await Future.delayed(const Duration(seconds: 2)); final newSession = Session( id: '${_mockSessions.length + 1}', courseId: courseId, name: name, startTime: DateTime.now(), endTime: DateTime.now().add(Duration(minutes: durationMinutes)), latitude: latitude, longitude: longitude, radius: radius, isActive: true, ); _mockSessions.add(newSession); return newSession; }

Future<List<User>> getStudents() async { await Future.delayed(const Duration(seconds: 1)); return [ User( id: 'student1', name: 'John Doe', email: 'john@example.com', role: 'student', studentId: 'S001', ), User( id: 'student2', name: 'Jane Smith', email: 'jane@example.com', role: 'student', studentId: 'S002', ), ]; }

Future<List<User>> getLecturers() async { await Future.delayed(const Duration(seconds: 1)); return [ User( id: 'lecturer1', name: 'Dr. Smith', email: 'smith@example.com', role: 'lecturer', lecturerId: 'L001', ), User( id: 'lecturer2', name: 'Prof. Johnson', email: 'johnson@example.com', role: 'lecturer', lecturerId: 'L002', ), ]; } } </jitFile>

<jitFile type="file" filePath="lib/services/location_service.dart"> import 'package:geolocator/geolocator.dart';

class LocationService { Future<bool> checkPermission() async { bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled) { return false; }

LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
  permission = await Geolocator.requestPermission();
  if (permission == LocationPermission.denied) {
    return false;
  }
}

if (permission == LocationPermission.deniedForever) {
  return false;
}

return true;

}

Future<Position> getCurrentPosition() async { bool hasPermission = await checkPermission(); if (!hasPermission) { throw Exception('Location permission not granted'); }

return await Geolocator.getCurrentPosition(
  desiredAccuracy: LocationAccuracy.high,
);

}

double calculateDistance( double startLatitude, double startLongitude, double endLatitude, double endLongitude, ) { return Geolocator.distanceBetween( startLatitude, startLongitude, endLatitude, endLongitude, ); } } </jitFile>

<jitFile type="file" filePath="lib/services/face_recognition_service.dart"> class FaceRecognitionService { Future<bool> verifyFace(String imageData, String userId) async { // In a real app, this would call an API or use ML Kit await Future.delayed(const Duration(seconds: 1)); return true; // Mock verification always succeeds } } </jitFile> <jitFile type="file" filePath="lib/widgets/app_logo.dart"> import 'package:flutter/material.dart';

class AppLogo extends StatelessWidget { final double size;

const AppLogo({super.key, this.size = 40});

@override Widget build(BuildContext context) { return Container( width: size, height: size, decoration: BoxDecoration( color: Theme.of(context).colorScheme.primary, borderRadius: BorderRadius.circular(8), ), child: const Center( child: Text( '🏫', style: TextStyle(fontSize: 24), ), ), ); } } </jitFile>

<jitFile type="file" filePath="lib/widgets/course_card.dart"> import 'package:flutter/material.dart'; import 'package:attendease/models/course.dart';

class CourseCard extends StatelessWidget { final Course course; final VoidCallback? onTap; final Widget? trailing;

const CourseCard({ super.key, required this.course, this.onTap, this.trailing, });

@override Widget build(BuildContext context) { return Card( margin: const EdgeInsets.symmetric(vertical: 8), child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(12), child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ const Icon(Icons.school, size: 40), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( course.title, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ), ), const SizedBox(height: 4), Text( course.code, style: TextStyle( color: Theme.of(context).textTheme.bodySmall?.color, ), ), ], ), ), if (trailing != null) trailing!, ], ), ), ), ); } } </jitFile>

<jitFile type="file" filePath="lib/widgets/session_card.dart"> import 'package:flutter/material.dart'; import 'package:attendease/models/session.dart'; import 'package:intl/intl.dart';

class SessionCard extends StatelessWidget { final Session session; final bool showCheckInButton; final VoidCallback? onCheckIn; final VoidCallback? onTap;

const SessionCard({ super.key, required this.session, this.showCheckInButton = false, this.onCheckIn, this.onTap, });

@override Widget build(BuildContext context) { final timeFormat = DateFormat.jm(); final dateFormat = DateFormat.yMMMd();

return Card(
  margin: const EdgeInsets.symmetric(vertical: 8),
  child: InkWell(
    onTap: onTap,
    borderRadius: BorderRadius.circular(12),
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text(
                session.name,
                style: const TextStyle(
                  fontWeight: FontWeight.bold,
                  fontSize: 16,
                ),
              ),
              Container(
                padding: const EdgeInsets.symmetric(
                  horizontal: 8,
                  vertical: 4,
                ),
                decoration: BoxDecoration(
                  color: session.isActive
                      ? Theme.of(context).colorScheme.secondary
                      : Colors.grey,
                  borderRadius: BorderRadius.circular(12),
                ),
                child: Text(
                  session.isActive ? 'Active' : 'Ended',
                  style: const TextStyle(
                    color: Colors.white,
                    fontSize: 12,
                  ),
                ),
              ),
            ],
          ),
          const SizedBox(height: 8),
          Row(
            children: [
              const Icon(Icons.access_time, size: 16),
              const SizedBox(width: 4),
              Text(
                '${dateFormat.format(session.startTime)} • ${timeFormat.format(session.startTime)} - ${timeFormat.format(session.endTime)}',
              ),
            ],
          ),
          const SizedBox(height: 8),
          Row(
            children: [
              const Icon(Icons.location_on, size: 16),
              const SizedBox(width: 4),
              Text('Within ${session.radius.round()}m radius'),
            ],
          ),
          if (showCheckInButton && session.isActive) ...[
            const SizedBox(height: 16),
            SizedBox(
              width: double.infinity,
              child: ElevatedButton.icon(
                onPressed: onCheckIn,
                style: ElevatedButton.styleFrom(
                  backgroundColor: Theme.of(context).colorScheme.secondary,
                ),
                icon: const Icon(Icons.camera_alt),
                label: const Text('Check In'),
              ),
            ),
          ],
        ],
      ),
    ),
  ),
);

} } </jitFile>

<jitFile type="file" filePath="lib/widgets/status_badge.dart"> import 'package:flutter/material.dart';

class StatusBadge extends StatelessWidget { final bool isActive; final String label;

const StatusBadge({ super.key, required this.isActive, required this.label, });

@override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: isActive ? Theme.of(context).colorScheme.secondary : Theme.of(context).colorScheme.error, borderRadius: BorderRadius.circular(12), ), child: Text( label, style: const TextStyle( color: Colors.white, fontSize: 12, ), ), ); } } </jitFile>

<jitFile type="file" filePath="lib/widgets/custom_button.dart"> import 'package:flutter/material.dart';

class CustomButton extends StatelessWidget { final String text; final IconData icon; final VoidCallback onPressed; final Color? backgroundColor; final Color? textColor; final bool isLoading;

const CustomButton({ super.key, required this.text, required this.icon, required this.onPressed, this.backgroundColor, this.textColor, this.isLoading = false, });

@override Widget build(BuildContext context) { return SizedBox( height: 50, child: ElevatedButton.icon( onPressed: isLoading ? null : onPressed, style: ElevatedButton.styleFrom( backgroundColor: backgroundColor ?? Theme.of(context).colorScheme.primary, foregroundColor: textColor ?? Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), icon: isLoading ? const SizedBox( width: 24, height: 24, child: CircularProgressIndicator( color: Colors.white, strokeWidth: 2, ), ) : Icon(icon), label: isLoading ? const SizedBox() : Text(text), ), ); } } </jitFile>

<jitFile type="file" filePath="lib/screens/shared/splash_screen.dart"> import 'package:flutter/material.dart'; import 'package:attendease/auth/auth_service.dart'; import 'package:attendease/screens/student/student_home.dart'; import 'package:attendease/screens/lecturer/lecturer_home.dart'; import 'package:attendease/screens/admin/admin_home.dart'; import 'package:attendease/auth/login_screen.dart'; import 'package:provider/provider.dart';

class SplashScreen extends StatefulWidget { const SplashScreen({super.key});

@override State<SplashScreen> createState() => _SplashScreenState(); }

class _SplashScreenState extends State<SplashScreen> { @override void initState() { super.initState(); _navigateToNextScreen(); }

Future<void> _navigateToNextScreen() async { await Future.delayed(const Duration(seconds: 2));

final authService = Provider.of<AuthService>(context, listen: false);
if (authService.token == null) {
  Navigator.pushReplacement(
    context,
    MaterialPageRoute(builder: (context) => const LoginScreen()),
  );
} else {
  switch (authService.userRole) {
    case 'student':
      Navigator.pushReplacement(
        context,
        MaterialPageRoute(builder: (context) => const StudentHomeScreen()),
      );
      break;
    case 'lecturer':
      Navigator.pushReplacement(
        context,
        MaterialPageRoute(builder: (context) => const LecturerHomeScreen()),
      );
      break;
    case 'admin':
      Navigator.pushReplacement(
        context,
        MaterialPageRoute(builder: (context) => const AdminHomeScreen()),
      );
      break;
    default:
      Navigator.pushReplacement(
        context,
        MaterialPageRoute(builder: (context) => const LoginScreen()),
      );
  }
}

}

@override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text( '🏫', style: TextStyle(fontSize: 64), ), const SizedBox(height: 16), Text( 'AttendEase', style: Theme.of(context).textTheme.headlineMedium?.copyWith( fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.primary, ), ), const SizedBox(height: 16), const CircularProgressIndicator(), ], ), ), ); } } </jitFile>

<jitFile type="file" filePath="lib/screens/shared/profile_screen.dart"> import 'package:flutter/material.dart'; import 'package:attendease/auth/auth_service.dart'; import 'package:provider/provider.dart';

class ProfileScreen extends StatelessWidget { const ProfileScreen({super.key});

@override Widget build(BuildContext context) { final authService = Provider.of<AuthService>(context); final theme = Theme.of(context);

return Scaffold(
  appBar: AppBar(
    title: const Text('Profile'),
  ),
  body: SingleChildScrollView(
    padding: const EdgeInsets.all(16),
    child: Column(
      children: [
        const CircleAvatar(
          radius: 50,
          child: Icon(Icons.person, size: 50),
        ),
        const SizedBox(height: 16),
        Text(
          authService.userName ?? 'No name',
          style: theme.textTheme.headlineSmall,
        ),
        const SizedBox(height: 8),
        Text(
          authService.userEmail ?? 'No email',
          style: theme.textTheme.bodyLarge,
        ),
        const SizedBox(height: 24),
        ListTile(
          leading: const Icon(Icons.edit),
          title: const Text('Edit Profile'),
          onTap: () {
            // Navigate to edit profile screen
          },
        ),
        const Divider(),
        ListTile(
          leading: const Icon(Icons.logout),
          title: const Text('Log Out'),
          onTap: () async {
            final confirmed = await showDialog<bool>(
              context: context,
              builder: (context) => AlertDialog(
                title: const Text('Log Out'),
                content: const Text('Are you sure you want to log out?'),
                actions: [
                  TextButton(
                    onPressed: () => Navigator.pop(context, false),
                    child: const Text('Cancel'),
                  ),
                  TextButton(
                    onPressed: () => Navigator.pop(context, true),
                    child: const Text('Log Out'),
                  ),
                ],
              ),
            );

            if (confirmed == true) {
              await authService.logout();
              Navigator.pushNamedAndRemoveUntil(
                context,
                '/login',
                (route) => false,
              );
            }
          },
        ),
      ],
    ),
  ),
);

} } </jitFile>

<jitFile type="file" filePath="lib/screens/student/student_home.dart"> import 'package:flutter/material.dart'; import 'package:attendease/services/api_service.dart'; import 'package:attendease/widgets/session_card.dart'; import 'package:provider/provider.dart'; import 'package:attendease/auth/auth_service.dart';

class StudentHomeScreen extends StatefulWidget { const StudentHomeScreen({super.key});

@override State<StudentHomeScreen> createState() => _StudentHomeScreenState(); }

class _StudentHomeScreenState extends State<StudentHomeScreen> { late Future<List<Session>> _sessionsFuture; final ApiService _apiService = ApiService();

@override void initState() { super.initState(); final authService = Provider.of<AuthService>(context, listen: false); _sessionsFuture = _apiService.getActiveSessionsForStudent(authService.token!); }

Future<void> _refreshSessions() async { final authService = Provider.of<AuthService>(context, listen: false); setState(() { _sessionsFuture = _apiService.getActiveSessionsForStudent(authService.token!); }); }

@override Widget build(BuildContext context) { final authService = Provider.of<AuthService>(context);

return Scaffold(
  appBar: AppBar(
    title: const Text('Home'),
  ),
  body: RefreshIndicator(
    onRefresh: _refreshSessions,
    child: FutureBuilder<List<Session>>(
      future: _sessionsFuture,
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return const Center(child: CircularProgressIndicator());
        }

        if (snapshot.hasError) {
          return Center(child: Text('Error: ${snapshot.error}'));
        }

        final sessions = snapshot.data ?? [];

        if (sessions.isEmpty) {
          return Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const Text('No active sessions right now.'),
                const SizedBox(height: 8),
                Text(
                  'Enjoy your day, ${authService.userName?.split(' ').first}!',
                  style: Theme.of(context).textTheme.titleMedium,
                ),
              ],
            ),
          );
        }

        return ListView.builder(
          padding: const EdgeInsets.all(16),
          itemCount: sessions.length,
          itemBuilder: (context, index) {
            final session = sessions[index];
            return SessionCard(
              session: session,
              showCheckInButton: true,
              onCheckIn: () {
                // Navigate to check-in screen
              },
            );
          },
        );
      },
    ),
  ),
);

} } </jitFile>

<jitFile type="file" filePath="lib/screens/student/student_courses.dart"> import 'package:flutter/material.dart'; import 'package:attendease/services/api_service.dart'; import 'package:attendease/widgets/course_card.dart'; import 'package:provider/provider.dart'; import 'package:attendease/auth/auth_service.dart'; import 'package:attendease/screens/student/course_detail_screen.dart';

class StudentCoursesScreen extends StatefulWidget { const StudentCoursesScreen({super.key});

@override State<StudentCoursesScreen> createState() => _StudentCoursesScreenState(); }

class _StudentCoursesScreenState extends State<StudentCoursesScreen> { late Future<List<Course>> _coursesFuture; final ApiService _apiService = ApiService();

@override void initState() { super.initState(); final authService = Provider.of<AuthService>(context, listen: false); _coursesFuture = _apiService.getCoursesForStudent(authService.token!); }

Future<void> _refreshCourses() async { final authService = Provider.of<AuthService>(context, listen: false); setState(() { _coursesFuture = _apiService.getCoursesForStudent(authService.token!); }); }

@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('My Courses'), ), body: RefreshIndicator( onRefresh: _refreshCourses, child: FutureBuilder<List<Course>>( future: _coursesFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); }

        if (snapshot.hasError) {
          return Center(child: Text('Error: ${snapshot.error}'));
        }

        final courses = snapshot.data ?? [];

        if (courses.isEmpty) {
          return Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const Text('No courses yet.'),
                const SizedBox(height: 16),
                ElevatedButton.icon(
                  onPressed: () {
                    // Show add course dialog
                  },
                  icon: const Icon(Icons.add),
                  label: const Text('Add Course'),
                ),
              ],
            ),
          );
        }

        return ListView.builder(
          padding: const EdgeInsets.all(16),
          itemCount: courses.length,
          itemBuilder: (context, index) {
            final course = courses[index];
            return CourseCard(
              course: course,
              onTap: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => CourseDetailScreen(course: course),
                  ),
                );
              },
              trailing: const Icon(Icons.chevron_right),
            );
          },
        );
      },
    ),
  ),
);

} } </jitFile>

<jitFile type="file" filePath="lib/screens/student/course_detail_screen.dart"> import 'package:flutter/material.dart'; import 'package:attendease/models/course.dart'; import 'package:attendease/services/api_service.dart'; import 'package:attendease/widgets/session_card.dart';

class CourseDetailScreen extends StatefulWidget { final Course course;

const CourseDetailScreen({super.key, required this.course});

@override State<CourseDetailScreen> createState() => _CourseDetailScreenState(); }

class _CourseDetailScreenState extends State<CourseDetailScreen> { late Future<List<Session>> _sessionsFuture; final ApiService _apiService = ApiService();

@override void initState() { super.initState(); _sessionsFuture = _apiService.getSessionsForCourse(widget.course.id); }

Future<void> _refreshSessions() async { setState(() { _sessionsFuture = _apiService.getSessionsForCourse(widget.course.id); }); }

@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.course.title), ), body: RefreshIndicator( onRefresh: _refreshSessions, child: FutureBuilder<List<Session>>( future: _sessionsFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); }

        if (snapshot.hasError) {
          return Center(child: Text('Error: ${snapshot.error}'));
        }

        final sessions = snapshot.data ?? [];

        return SingleChildScrollView(
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                widget.course.code,
                style: Theme.of(context).textTheme.titleMedium,
              ),
              const SizedBox(height: 8),
              if (widget.course.description != null)
                Text(widget.course.description!),
              const SizedBox(height: 16),
              const Text(
                'Attendance Sessions',
                style: TextStyle(
                  fontWeight: FontWeight.bold,
                  fontSize: 18,
                ),
              ),
              const SizedBox(height: 8),
              if (sessions.isEmpty)
                const Center(
                  child: Text('No sessions for this course yet.'),
                )
              else
                ...sessions.map(
                  (session) => SessionCard(
                    session: session,
                    showCheckInButton: session.isActive,
                    onCheckIn: () {
                      // Navigate to check-in screen
                    },
                  ),
                ),
            ],
          ),
        );
      },
    ),
  ),
);

} } </jitFile>

<jitFile type="file" filePath="lib/screens/student/checkin_camera_screen.dart"> import 'package:flutter/material.dart'; import 'package:camera/camera.dart'; import 'package:attendease/services/location_service.dart'; import 'package:attendease/services/face_recognition_service.dart'; import 'package:attendease/widgets/custom_button.dart';

class CheckInCameraScreen extends StatefulWidget { final String sessionId;

const CheckInCameraScreen({super.key, required this.sessionId});

@override State<CheckInCameraScreen> createState() => _CheckInCameraScreenState(); }

class _CheckInCameraScreenState extends State<CheckInCameraScreen> { late CameraController _cameraController; late Future<void> _initializeControllerFuture; bool _isLoading = false; bool _isCheckingLocation = false; final LocationService _locationService = LocationService(); final FaceRecognitionService _faceService = FaceRecognitionService();

@override void initState() { super.initState(); _initializeCamera(); }

Future<void> _initializeCamera() async { final cameras = await availableCameras(); final frontCamera = cameras.firstWhere( (camera) => camera.lensDirection == CameraLensDirection.front, );

_cameraController = CameraController(
  frontCamera,
  ResolutionPreset.medium,
);

_initializeControllerFuture = _cameraController.initialize();

}

Future<void> _checkIn() async { setState(() => _isCheckingLocation = true);

try {
  // 1. Check location permission and get current position
  final hasPermission = await _locationService.checkPermission();
  if (!hasPermission) {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(
        content: Text('Location permission is required for check-in'),
      ),
    );
    return;
  }

  final position = await _locationService.getCurrentPosition();

  // 2. Take picture
  setState(() {
    _isCheckingLocation = false;
    _isLoading = true;
  });

  final image = await _cameraController.takePicture();

  // 3. Verify face (mock implementation)
  final isVerified = await _faceService.verifyFace(
    image.path,
    'student1', // In real app, use actual student ID
  );

  if (!isVerified) {

_

Updated files
v1
are u done

}

  // 4. If everything is successful, show success message
  if (mounted) {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(
        content: Text('Check-in successful!'),
        backgroundColor: Colors.green,
      ),
    );
    Navigator.pop(context);
  }
} catch (e) {
  if (mounted) {
    ScaffoldMessenger.ofcontext).showSnackBar(
      SnackBar(
        content: Text('Error: ${e.toString()}'),
        backgroundColor: Colors.red,
      ),
    );
  }
} finally {
  if (mounted) {
    setState(() {
      _isLoading = false;
      _isCheckingLocation = false;
    });
  }
}

}

@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Check-in'), ), body: FutureBuilder( future: _initializeControllerFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { return Stack( children: [ CameraPreview(_cameraController), Positioned( bottom: 0, left: 0, right: 0, child: Container( padding: const EdgeInsets.all(16), color: Colors.black54, child: Column( children: [ Text( 'Session ID: ${widget.sessionId}', style: const TextStyle( color: Colors.white, fontSize: 16, ), ), const SizedBox(height: 8), CustomButton( text: 'Check-in', icon: Icons.camera_alt, onPressed: _checkIn, backgroundColor: Colors.green, textColor: Colors.white, isLoading: _isLoading, ), ], ), ), ), if (_isCheckingLocation) const Center( child: CircularProgressIndicator(), ), ], ); } else { return const Center(child: CircularProgressIndicator()); } }, ), ); }

@override void dispose() { _cameraController.dispose(); super.dispose(); } } </jitFile>

<jitFile type="file" path="lib/screens/lecturer/lecturer_home.dart"> import 'package:flutter/material.dart'; import 'package:attendease/services/api_service.dart'; import 'package:provider/provider.dart'; import 'package:attendease/auth/auth_service.dart'; import 'package:attendease/widgets/session_card.dart';

class LecturerHomeScreen extends StatefulWidget { const LecturerHomeScreen({super.key});

@override State<LecturerHomeScreen> createState() => _LecturerHomeScreenState(); }

class _LecturerHomeScreenState extends State<LecturerHomeScreen> { late Future<List<Course>> _courseFuture; final ApiService _apiService = ApiService();

@override void initState() { super.initState(); final authService = Provider.of<AuthService>(context, listen: false); _courseFuture = _apiService.getCourseForLecturer(authService.token!); }

Future<void> _refreshCourses() async { final authService = Provider.of<AuthService>(context, listen: false); setState(() { _courseFuture = _apiService.getCourseForLecturer(authService.token!); }); }

@override Widget build(BuildContext context) { final authService = Provider.of<AuthService>(context);

return Scaffold(
  appBar: AppBar(
    title: const Text('Dashboard'),
  ),
  body: RefreshIndicator(
    onRefresh: _refreshCourses,
    child: FutureBuilder<List<Course>>(
      future: _courseFuture,
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return const Center(child: CircularProgressIndicator());
        }

        if (snapshot.hasError) {
          return Center(child: Text('Error: ${snapshot.error}'));
        }

        final courses = snapshot.data ?? [];

        if (courses.isEmpty) {
          return Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const Text('No courses yet.'),
                const SizedBox(height: 16),
                ElevatedButton.icon(
                  onPressed: () {
                    // Show add course dialog
                  },
                  icon: const Icon(Icons.add),
                  label: const Text('Add Course'),
                ),
              ],
            ),
          );
        }

        return ListView.builder(
          padding: const EdgeInsets.all(16),
          itemCount: courses.length,
          itemBuilder: (context, index) {
            final course = courses[index];
            return SessionCard(
              session: Session(
                id: course.id,
                courseId: course.id,
                name: course.title,
                startTime: DateTime.now(),
                endTime: DateTime.now().add(const Duration(hours: 1)),
                latitude: 0,
                longitude: 0,
                radius: 100,
                isActive: true,
              ),
              showCheckInButton: false,
            );
          },
        );
      },
    ),
  ),
);

} } </jitFile>

<jitFile type="file" path="lib/screens/lecturer/lecturer_courses.dart"> import 'package:flutter/material.dart'; import 'package:attendease/services/api_service.dart'; import 'package:provider/provider.dart'; import 'package:attendease/auth/auth_service.dart'; import 'package:attendease/widgets/course_card.dart';

class LecturerCoursesScreen extends StatefulWidget { const LecturerCoursesScreen({super.key});

@override State<LecturerCoursesScreen> createState() => _LecturerCoursesScreenState(); }

class _LecturerCoursesScreenState extends State<LecturerCoursesScreen> { late Future<List<Course>> _courseFuture; final ApiService _apiService = ApiService();

@override void initState() { super.initState(); final authService = Provider.of<AuthService>(context, listen: false); _courseFuture = _apiService.getCourseForLecturer(authService.token!); }

Future<void> _refreshCourses() async { final authService = Provider.of<AuthService>(context, listen: false); setivity, I'll continue with the implementation of the lecturer courses screen and other screens as per your requirements.

Here's the continuation of the lecturer courses screen:

    setState(() {
      _courseFuture = _apiService.getCourseForLecturer(authService.token!);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('My Courses'),
      ),
      body: RefreshIndicator(
        onRefresh: _refreshCourses,
        child: FutureBuilder<List<Course>>(
          future: _courseFuture,
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              return const Center(child: CircularProgressIndicator());
            }

            if (snapshot.hasError) {
              return Center(child: Text('Error: ${snapshot.error}'));
            }

            final courses = snapshot.data ?? [];

            if (courses.isEmpty) {
              return Center(
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    const Text('No courses yet.'),
                    const SizedBox(height: 16),
                    ElevatedButton.icon(
                      onPressed: () {
                        // Show add course dialog
                      },
                      icon: const Icon(Icons.add),
                      label: const Text('Add Course'),
                    ),
                  ],
                ),
              );
            }

            return ListView.builder(
              padding: const EdgeInsets.all(16),
              itemCount: courses.length,
              itemBuilder: (context, index) {
                final course = courses[index];
                return CourseCard(
                  course: course,
                  onTap: () {
                    // Navigate to course details
                  },
                );
              },
            );
          },
        ),
      ),
    );
  }
}

Would you like me to continue with the implementation of the remaining screens (lecturer reports, lecturer profile, admin screens, etc.)? I can provide the complete implementation for all screens as per your requirements.

Please let me know if you'd like me to proceed with the remaining screens or if you'd like to see any specific screen implementation first._