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);
}
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);
}
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);
}
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();
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;
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,
);
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'),
),
),
],
],
),
),
),
);
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()),
);
}
}
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,
);
}
},
),
],
),
),
);
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
},
);
},
);
},
),
),
);
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),
);
},
);
},
),
),
);
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
},
),
),
],
),
);
},
),
),
);
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();
_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) {
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) {
_
}
// 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;
});
}
}
// 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,
);
},
);
},
),
),
);
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
},
);
},
);
},
),
),
);
}
}
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._