diff --git a/frontend/app_student/.env.dist b/frontend/app_student/.env.dist index 4873f58..317cd49 100644 --- a/frontend/app_student/.env.dist +++ b/frontend/app_student/.env.dist @@ -1,2 +1,2 @@ -DEV_API_URL= -PROD_API_URL= \ No newline at end of file +DEV_API_URL=https://api-dev.lukasvalois.com +PROD_API_URL=https://api.lukasvalois.com \ No newline at end of file diff --git a/frontend/app_student/lib/api/account/repositories/account_repository.dart b/frontend/app_student/lib/api/account/repositories/account_repository.dart new file mode 100644 index 0000000..0fc90e4 --- /dev/null +++ b/frontend/app_student/lib/api/account/repositories/account_repository.dart @@ -0,0 +1,64 @@ +import 'dart:io'; +import 'package:app_student/api/api_service.dart'; +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'package:path_provider/path_provider.dart'; + +import '../../../utils/global.dart'; + +class AccountRepository { + final ApiService apiService; + + AccountRepository({required this.apiService}); + + Future login(String username, String password) async { + final response = await http.post( + Uri.parse('${apiService.apiUrl}/api/student/login'), + body: {'username': username, 'password': password}, + ); + + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + final studentId = (data['studentId']); + Global.setStudentId(studentId); + return studentId; + } else { + throw Exception('Failed to login'); + } + } + + Future getMarks(int studentId) async { + final response = await http.get( + Uri.parse('${apiService.apiUrl}/api/student/marks/$studentId'), + ); + + if (response.statusCode == 200) { + return _savePdfFile(response.bodyBytes, 'marks.pdf'); + } else { + throw Exception('Failed to get marks'); + } + } + + Future getAbsences(int studentId) async { + final response = await http.get( + Uri.parse('${apiService.apiUrl}/api/student/absences/$studentId'), + ); + + if (response.statusCode == 200) { + return _savePdfFile(response.bodyBytes, 'absences.pdf'); + } else { + throw Exception('Failed to get absences'); + } + } + + Future _savePdfFile(List bytes, String filename) async { + final directory = await getApplicationDocumentsDirectory(); + final path = directory.path; + final file = File('$path/$filename'); + + // Écrire les bytes de la réponse dans le fichier + await file.writeAsBytes(bytes); + + return file; + } +} diff --git a/frontend/app_student/lib/api/users/entities/user_entity.dart b/frontend/app_student/lib/api/users/entities/user_entity.dart index 7aa7b72..38d03a7 100644 --- a/frontend/app_student/lib/api/users/entities/user_entity.dart +++ b/frontend/app_student/lib/api/users/entities/user_entity.dart @@ -1,12 +1,20 @@ +import 'dart:io'; + class UserEntity { - final String ine; + final String? ine; final String firstName; - final DateTime birthDate; + final DateTime? birthDate; final String? className; + final int? studentId; + final File? marksFile; + final File? absencesFile; UserEntity( {required this.ine, required this.firstName, required this.birthDate, - this.className}); + this.className, + this.studentId, + this.marksFile, + this.absencesFile}); } diff --git a/frontend/app_student/lib/api/users/models/user_model.dart b/frontend/app_student/lib/api/users/models/user_model.dart index 1e1eb11..35a925d 100644 --- a/frontend/app_student/lib/api/users/models/user_model.dart +++ b/frontend/app_student/lib/api/users/models/user_model.dart @@ -1,12 +1,25 @@ -import '../entities/user_entity.dart'; +import 'dart:io'; + +import 'package:app_student/api/users/entities/user_entity.dart'; class UserModel { final UserEntity entity; UserModel({required this.entity}); - String get file => entity.ine; + String get firstName => entity.firstName; + String get name => entity.firstName; - DateTime get birthDate => entity.birthDate; + + DateTime? get birthDate => entity.birthDate; + String? get className => entity.className; + + String? get ine => entity.ine; + + int? get studentId => entity.studentId; + + File? get marksFile => entity.marksFile; + + File? get absencesFile => entity.absencesFile; } diff --git a/frontend/app_student/lib/api/users/repositories/user_repository.dart b/frontend/app_student/lib/api/users/repositories/user_repository.dart index ff6096c..4e7a058 100644 --- a/frontend/app_student/lib/api/users/repositories/user_repository.dart +++ b/frontend/app_student/lib/api/users/repositories/user_repository.dart @@ -1,45 +1,78 @@ +import 'dart:convert'; +import 'dart:io'; import 'package:app_student/api/users/models/user_model.dart'; +import 'package:intl/intl.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; - +import '../../../utils/global.dart'; +import '../../api_service.dart'; import '../entities/user_entity.dart'; +import 'package:http/http.dart' as http; class UserRepository { + final ApiService apiService; + + UserRepository({required this.apiService}); + Future getUser() async { final SharedPreferences prefs = await SharedPreferences.getInstance(); - String? ine = prefs.getString('ine'); String? name = prefs.getString('name'); - String? birthDate = prefs.getString('birthDate'); String? className = prefs.getString('className'); - if (ine != null && name != null && birthDate != null) { - return UserModel( - entity: UserEntity( - ine: ine, + String? ine = prefs.getString('ine'); + String? birthDate = prefs.getString('birthDate'); + int? studentId = prefs.getInt('studentId'); + + File marksFile = File(''); + File absencesFile = File(''); + + if (studentId != null) { + marksFile = await getMarks(studentId); + absencesFile = await getAbsences(studentId); + } + + if (name == null) { + throw Exception('User name not found'); + } + + DateTime bd = DateTime.now(); + if (birthDate != null) { + bd = DateFormat('dd/MM/yyyy').parse(birthDate); + } + + return UserModel( + entity: UserEntity( firstName: name, - birthDate: DateTime.parse(birthDate), className: className, - ), - ); - } else { - throw Exception('Utilisateur non trouvé'); - } + ine: ine, + birthDate: bd, + studentId: studentId, + marksFile: marksFile, + absencesFile: absencesFile), + ); } - Future saveUserDetails( - String ine, String name, String birthDate, String className) async { + Future saveUserDetails(String name, String? className, + {String? ine, String? birthDate}) async { SharedPreferences prefs = await SharedPreferences.getInstance(); UserModel( entity: UserEntity( - ine: ine, firstName: name, - birthDate: DateTime.parse(birthDate), className: className, + ine: ine, + birthDate: birthDate != null ? DateTime.parse(birthDate) : null, ), ); - await prefs.setString('ine', ine); await prefs.setString('name', name); - await prefs.setString('birthDate', birthDate); - await prefs.setString('className', className); + if (className != null) { + await prefs.setString('className', className); + } + if (ine != null) { + await prefs.setString('ine', ine); + } + if (birthDate != null) { + await prefs.setString('birthDate', birthDate); + } } Future deleteUser() async { @@ -61,10 +94,62 @@ class UserRepository { await prefs.remove('name'); await prefs.remove('birthDate'); await prefs.remove('className'); + await prefs.remove('studentId'); } Future clearClass() async { SharedPreferences prefs = await SharedPreferences.getInstance(); await prefs.remove('className'); } + + Future login(String username, String password) async { + final response = await http.post( + Uri.parse('${apiService.apiUrl}/api/student/login'), + body: {'username': username, 'password': password}, + ); + + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + final studentId = (data['studentId']); + Global.setStudentId(studentId); + return studentId; + } else { + throw Exception('Failed to login'); + } + } + + Future getMarks(int studentId) async { + final response = await http.get( + Uri.parse('${apiService.apiUrl}/api/student/marks/$studentId'), + ); + + if (response.statusCode == 200) { + return _savePdfFile(response.bodyBytes, 'marks.pdf'); + } else { + throw Exception('Failed to get marks'); + } + } + + Future getAbsences(int studentId) async { + final response = await http.get( + Uri.parse('${apiService.apiUrl}/api/student/absences/$studentId'), + ); + + if (response.statusCode == 200) { + return _savePdfFile(response.bodyBytes, 'absences.pdf'); + } else { + throw Exception('Failed to get absences'); + } + } + + Future _savePdfFile(List bytes, String filename) async { + final directory = await getApplicationDocumentsDirectory(); + final path = directory.path; + final file = File('$path/$filename'); + + // Écrire les bytes de la réponse dans le fichier + await file.writeAsBytes(bytes); + + return file; + } } diff --git a/frontend/app_student/lib/class_groups/views/class_group.dart b/frontend/app_student/lib/class_groups/views/class_group.dart index 3bea402..250275e 100644 --- a/frontend/app_student/lib/class_groups/views/class_group.dart +++ b/frontend/app_student/lib/class_groups/views/class_group.dart @@ -42,7 +42,7 @@ class ClassGroupPage extends StatelessWidget { return const Center(child: CircularProgressIndicator()); } else if (userState is UserLoading) { return const Center(child: CircularProgressIndicator()); - } else if (userState is UserLoaded) { + } else if (userState is UserWithoutClass) { final user = userState.user; return BlocBuilder( builder: (context, classState) { @@ -55,9 +55,7 @@ class ClassGroupPage extends StatelessWidget { .classSelectionTitle(user.name)), HeaderSubtitle(AppLocalizations.of(context)! .classSelectionSubtitle), - Expanded( - child: CardList(classesList: classState.classes), - ), + CardList(classesList: classState.classes), ], ); } else if (classState is ClassGroupError) { diff --git a/frontend/app_student/lib/class_groups/views/widgets/card_list.dart b/frontend/app_student/lib/class_groups/views/widgets/card_list.dart index 253c768..584f328 100644 --- a/frontend/app_student/lib/class_groups/views/widgets/card_list.dart +++ b/frontend/app_student/lib/class_groups/views/widgets/card_list.dart @@ -11,6 +11,7 @@ class CardList extends StatelessWidget { @override Widget build(BuildContext context) { return ListView.builder( + shrinkWrap: true, itemCount: classesList.length, itemBuilder: (context, index) { return Card( diff --git a/frontend/app_student/lib/l10n/app_fr.arb b/frontend/app_student/lib/l10n/app_fr.arb index e70de85..79d3ef7 100644 --- a/frontend/app_student/lib/l10n/app_fr.arb +++ b/frontend/app_student/lib/l10n/app_fr.arb @@ -129,5 +129,9 @@ "confirm": "Confirmer", "@confirm": { "description": "Texte du bouton pour confirmer" + }, + "my3il": "My3il", + "@my3il": { + "description": "Texte du menu" } } \ No newline at end of file diff --git a/frontend/app_student/lib/login/cubit/login_cubit.dart b/frontend/app_student/lib/login/cubit/login_cubit.dart index 81cb043..eca525b 100644 --- a/frontend/app_student/lib/login/cubit/login_cubit.dart +++ b/frontend/app_student/lib/login/cubit/login_cubit.dart @@ -11,21 +11,19 @@ class LoginCubit extends Cubit { checkUserAuthentication(); } - Future areFieldsFilled( - String ine, String name, String birthDate) async { - if (ine.isEmpty || name.isEmpty || birthDate.isEmpty) { + Future areFieldsFilled(String name) async { + if (name.isEmpty) { return false; } return true; } - Future saveLoginDetails( - String ine, String name, String birthDate) async { - if (!(await areFieldsFilled(ine, name, birthDate))) { + Future saveLoginDetails(String name) async { + if (!(await areFieldsFilled(name))) { emit(LoginFieldError()); return; } - await userRepository.saveUserDetails(ine, name, birthDate, ''); + await userRepository.saveUserDetails(name, '', ine: null, birthDate: null); emit(RedirectToClassSelection()); } diff --git a/frontend/app_student/lib/login/views/login_view.dart b/frontend/app_student/lib/login/views/login_view.dart index 8634857..a2f0d99 100644 --- a/frontend/app_student/lib/login/views/login_view.dart +++ b/frontend/app_student/lib/login/views/login_view.dart @@ -1,15 +1,17 @@ -import 'package:app_student/login/views/widgets/form/form_login.dart'; import 'package:app_student/utils/custom_layout.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter/material.dart'; import '../../shared_components/header_logo.dart'; import '../../shared_components/header_title.dart'; import '../../utils/global.dart'; +import 'widgets/form/button_submit.dart'; +import 'widgets/form/inputs/input_prenom.dart'; class LoginView extends StatelessWidget { final String title; + final TextEditingController nameController = TextEditingController(); - const LoginView(this.title, {super.key}); + LoginView(this.title, {super.key}); @override Widget build(BuildContext context) { @@ -18,9 +20,10 @@ class LoginView extends StatelessWidget { body: Column( children: [ HeaderTitle(title), - const Expanded(child: FormLogin()), + FormLogin(nameController: nameController), ], ), + bottomContent: SubmitButton(nameController: nameController), bottomBar: Padding( padding: const EdgeInsets.all(10.0), child: Text( @@ -35,3 +38,22 @@ class LoginView extends StatelessWidget { ); } } + +class FormLogin extends StatelessWidget { + final TextEditingController nameController; + + const FormLogin({required this.nameController, super.key}); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 10.0), + FirstnameTextField(controller: nameController), + const SizedBox(height: 30.0), + ], + ); + } +} diff --git a/frontend/app_student/lib/login/views/widgets/form/button_submit.dart b/frontend/app_student/lib/login/views/widgets/form/button_submit.dart index 45789fa..dbccac7 100644 --- a/frontend/app_student/lib/login/views/widgets/form/button_submit.dart +++ b/frontend/app_student/lib/login/views/widgets/form/button_submit.dart @@ -1,57 +1,28 @@ import 'package:app_student/login/cubit/login_cubit.dart'; +import 'package:app_student/utils/custom_button.dart'; import 'package:app_student/utils/custom_theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class SubmitButton extends StatelessWidget { - final TextEditingController ineController; final TextEditingController nameController; - final TextEditingController birthDateController; - final DateTime birthDate; - const SubmitButton( - {super.key, - required this.ineController, - required this.nameController, - required this.birthDateController, - required this.birthDate}); + const SubmitButton({ + super.key, + required this.nameController, + }); @override Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: double.infinity, - height: 50.0, - child: ElevatedButton( - style: ButtonStyle( - textStyle: MaterialStateProperty.all( - CustomTheme.text.toBold, - ), - backgroundColor: - MaterialStateProperty.all(CustomTheme.secondaryColor), - foregroundColor: MaterialStateProperty.all(Colors.white), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(3.0), - ), - ), - ), - onPressed: () { - final String ine = ineController.text.trim(); - final String name = nameController.text.trim(); - final String birthDate = birthDateController.text.trim(); - - context.read().saveLoginDetails(ine, name, birthDate); - }, - child: Text( - AppLocalizations.of(context)!.loginButton, - ), - ), - ), - ], + return CustomButton( + text: AppLocalizations.of(context)!.loginButton, + onPressed: () { + final String name = nameController.text.trim(); + context.read().saveLoginDetails(name); + }, + backgroundColor: CustomTheme.secondaryColor, + textColor: Colors.white, ); } } diff --git a/frontend/app_student/lib/login/views/widgets/form/form_login.dart b/frontend/app_student/lib/login/views/widgets/form/form_login.dart index 965ff4d..8b13789 100644 --- a/frontend/app_student/lib/login/views/widgets/form/form_login.dart +++ b/frontend/app_student/lib/login/views/widgets/form/form_login.dart @@ -1,60 +1 @@ -import 'package:flutter/material.dart'; -import 'button_submit.dart'; -import 'inputs/input_prenom.dart'; -class FormLogin extends StatefulWidget { - const FormLogin({super.key}); - - @override - FormLoginState createState() => FormLoginState(); -} - -class FormLoginState extends State { - final TextEditingController ineController = - TextEditingController(text: '999999999'); - final TextEditingController nameController = TextEditingController(); - final TextEditingController birthDateController = - TextEditingController(text: '2000-01-01'); - DateTime birthDate = DateTime.now(); - - @override - Widget build(BuildContext context) { - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints viewportConstraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: viewportConstraints.maxHeight, - ), - child: Stack( - children: [ - Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const SizedBox(height: 10.0), - const SizedBox(height: 10.0), - // ceci est temporaire, le laisser tant qu'on ne remet pas les autres inputs - FirstnameTextField(controller: nameController), - const SizedBox(height: 10.0), - ], - ), - Positioned( - bottom: 0, - left: 0, - right: 0, - child: SubmitButton( - ineController: ineController, - nameController: nameController, - birthDateController: birthDateController, - birthDate: birthDate, - ), - ), - ], - ), - ), - ); - }, - ); - } -} diff --git a/frontend/app_student/lib/main_dev.dart b/frontend/app_student/lib/main_dev.dart index 1c65c51..980aab9 100644 --- a/frontend/app_student/lib/main_dev.dart +++ b/frontend/app_student/lib/main_dev.dart @@ -48,15 +48,7 @@ class MyApp extends StatelessWidget { return MaterialApp.router( debugShowCheckedModeBanner: false, title: '3iL Student App', - theme: ThemeData( - primarySwatch: Colors.blue, - // Orange 3IL - focusColor: CustomTheme.secondaryColor, - primaryColor: CustomTheme.primaryColor, - // Bleu 3IL - secondaryHeaderColor: CustomTheme.primaryColor, - fontFamily: 'Arial', - ), + theme: CustomTheme.theme, localizationsDelegates: const [ AppLocalizations.delegate, GlobalMaterialLocalizations.delegate, diff --git a/frontend/app_student/lib/menu/menu_view.dart b/frontend/app_student/lib/menu/menu_view.dart index 9a7b4ef..cf1669a 100644 --- a/frontend/app_student/lib/menu/menu_view.dart +++ b/frontend/app_student/lib/menu/menu_view.dart @@ -1,12 +1,8 @@ import 'package:app_student/routes.dart'; import 'package:app_student/utils/custom_theme.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:fluttertoast/fluttertoast.dart'; import 'package:go_router/go_router.dart'; - -import '../users/cubit/user_cubit.dart'; import 'menu_item.dart'; class MenuBarView extends StatefulWidget { @@ -44,16 +40,7 @@ class MenuBarViewState extends State { switch (index) { case 0: - context.read().deleteUser(); - GoRouter.of(context).go(AppRoutes.loginPage); - WidgetsBinding.instance.addPostFrameCallback((_) { - Fluttertoast.showToast( - msg: AppLocalizations.of(context)!.disconnectedMessage, - toastLength: Toast.LENGTH_LONG, - gravity: ToastGravity.BOTTOM, - textColor: Colors.white, - ); - }); + GoRouter.of(context).go(AppRoutes.accountPage); break; case 1: GoRouter.of(context).go(AppRoutes.schedulePage); @@ -69,10 +56,10 @@ class MenuBarViewState extends State { return BottomNavigationBar( items: [ MenuIcon( - iconPath: 'assets/images/disconnect.svg', + iconPath: 'assets/images/school.svg', selectedIndex: _selectedIndex, itemIndex: 0, - label: AppLocalizations.of(context)!.disconnect, + label: AppLocalizations.of(context)!.my3il, ), MenuIcon( iconPath: 'assets/images/calendar.svg', diff --git a/frontend/app_student/lib/profil/views/profil.dart b/frontend/app_student/lib/profil/views/profil.dart new file mode 100644 index 0000000..bb6ef3d --- /dev/null +++ b/frontend/app_student/lib/profil/views/profil.dart @@ -0,0 +1,109 @@ +import 'package:app_student/menu/menu_view.dart'; +import 'package:app_student/profil/views/widgets/user_class_card.dart'; +import 'package:app_student/routes.dart'; +import 'package:app_student/utils/custom_layout.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:fluttertoast/fluttertoast.dart'; +import 'package:go_router/go_router.dart'; +import 'package:intl/intl.dart'; + +import '../../shared_components/header_logo.dart'; +import '../../shared_components/header_title.dart'; +import '../../users/cubit/user_cubit.dart'; +import '../../utils/custom_button.dart'; +import '../../utils/global.dart'; +import 'widgets/user_info_card.dart'; + +class ProfilPage extends StatelessWidget { + const ProfilPage({super.key}); + + @override + Widget build(BuildContext context) { + return CustomLayout( + appBar: HeaderLogo(appBarHeight: Global.screenHeight * 0.3), + body: BlocBuilder( + builder: (context, state) { + if (state is UserLoading) { + return Container(); + } else if (state is UserNameLoaded) { + final user = state.user; + return Column( + children: [ + Padding( + padding: const EdgeInsets.only(left: 10.0), + child: HeaderTitle( + AppLocalizations.of(context)! + .profilMessageTitle(user.firstName), + ), + ), + UserClassCard( + className: user.className!, + firstName: user.firstName, + onTap: () { + context.read().clearUserClass(); + GoRouter.of(context).go(AppRoutes.classListPage); + }, + ), + ], + ); + } else if (state is UserLoaded) { + final user = state.user; + + final birthDateString = + DateFormat('dd/MM/yyyy').format(user.birthDate!); + + return Column( + children: [ + Padding( + padding: const EdgeInsets.only(left: 10.0), + child: HeaderTitle( + AppLocalizations.of(context)! + .profilMessageTitle(user.firstName), + ), + ), + UserClassCard( + className: user.className!, + firstName: user.firstName, + onTap: () { + context.read().clearUserClass(); + GoRouter.of(context).go(AppRoutes.classListPage); + }, + ), + UserInfoCard(ine: user.ine!, birthDate: birthDateString), + ], + ); + } else { + return const SizedBox.shrink(); + } + }, + ), + bottomContent: BlocBuilder( + builder: (context, state) { + if (state is UserLoading) { + return const Center(child: CircularProgressIndicator()); + } else { + return CustomButton( + text: AppLocalizations.of(context)!.disconnect, + onPressed: () async { + final goRouter = GoRouter.of(context); + await context.read().deleteUser(); + goRouter.go(AppRoutes.loginPage); + WidgetsBinding.instance.addPostFrameCallback((_) { + Fluttertoast.showToast( + msg: AppLocalizations.of(context)!.disconnectedMessage, + toastLength: Toast.LENGTH_LONG, + gravity: ToastGravity.BOTTOM, + textColor: Colors.white, + ); + }); + }, + ); + } + }, + ), + bottomBar: const MenuBarView(), + ); + } +} diff --git a/frontend/app_student/lib/profils/views/widgets/class_group_button.dart b/frontend/app_student/lib/profil/views/widgets/class_group_button.dart similarity index 100% rename from frontend/app_student/lib/profils/views/widgets/class_group_button.dart rename to frontend/app_student/lib/profil/views/widgets/class_group_button.dart diff --git a/frontend/app_student/lib/profil/views/widgets/user_class_card.dart b/frontend/app_student/lib/profil/views/widgets/user_class_card.dart new file mode 100644 index 0000000..1c0d896 --- /dev/null +++ b/frontend/app_student/lib/profil/views/widgets/user_class_card.dart @@ -0,0 +1,44 @@ +import 'package:app_student/utils/custom_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; + +class UserClassCard extends StatelessWidget { + final String className; + final String firstName; + final VoidCallback onTap; + + const UserClassCard({ + required this.className, + required this.firstName, + required this.onTap, + super.key, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Card( + child: ListTile( + leading: SizedBox( + width: 50, + child: ColorFiltered( + colorFilter: const ColorFilter.mode( + CustomTheme.primaryColor, BlendMode.srcIn), + child: SvgPicture.asset( + 'assets/images/user.svg', + width: 30, + height: 30, + ), + ), + ), + title: Text(className, style: CustomTheme.textXl), + subtitle: Text(firstName, style: CustomTheme.text), + ), + ), + ), + ); + } +} diff --git a/frontend/app_student/lib/profils/views/widgets/user_info_card.dart b/frontend/app_student/lib/profil/views/widgets/user_info_card.dart similarity index 100% rename from frontend/app_student/lib/profils/views/widgets/user_info_card.dart rename to frontend/app_student/lib/profil/views/widgets/user_info_card.dart diff --git a/frontend/app_student/lib/profils/views/profil.dart b/frontend/app_student/lib/profils/views/profil.dart deleted file mode 100644 index 573b3db..0000000 --- a/frontend/app_student/lib/profils/views/profil.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:app_student/menu/menu_view.dart'; -import 'package:app_student/profils/views/widgets/class_group_button.dart'; -import 'package:app_student/profils/views/widgets/user_class_card.dart'; -import 'package:app_student/utils/custom_layout.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; - -import '../../shared_components/header_logo.dart'; -import '../../shared_components/header_title.dart'; -import '../../users/cubit/user_cubit.dart'; -import '../../utils/global.dart'; - -class ProfilPage extends StatelessWidget { - const ProfilPage({super.key}); - - @override - Widget build(BuildContext context) { - final userState = context.watch().state; - String firstName = ''; - String className = ''; - // String ine = ''; - // DateTime? birthDate; - - if (userState is UserLoaded) { - firstName = userState.user.entity.firstName; - className = userState.user.entity.className!; - // ine = userState.user.entity.ine; - // birthDate = userState.user.entity.birthDate; - } - - // String birthDateString = birthDate != null - // ? DateFormat('dd/MM/yyyy').format(birthDate) - // : 'error'; - - return CustomLayout( - appBar: HeaderLogo(appBarHeight: Global.screenHeight * 0.3), - body: Column( - children: [ - Padding( - padding: const EdgeInsets.only(left: 10.0), - child: HeaderTitle( - AppLocalizations.of(context)!.profilMessageTitle(firstName), - ), - ), - UserClassCard(className: className, firstName: firstName), - // UserInfoCard(ine: ine, birthDate: birthDateString), - ], - ), - bottomContent: const ClassGroupButton(), - bottomBar: const MenuBarView(), - ); - } -} diff --git a/frontend/app_student/lib/profils/views/widgets/user_class_card.dart b/frontend/app_student/lib/profils/views/widgets/user_class_card.dart deleted file mode 100644 index db64c2f..0000000 --- a/frontend/app_student/lib/profils/views/widgets/user_class_card.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:app_student/utils/custom_theme.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; - -class UserClassCard extends StatelessWidget { - final String className; - final String firstName; - - const UserClassCard( - {super.key, required this.className, required this.firstName}); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(10.0), - child: Card( - child: ListTile( - leading: SizedBox( - width: 50, - child: ColorFiltered( - colorFilter: const ColorFilter.mode( - CustomTheme.primaryColor, BlendMode.srcIn), - child: SvgPicture.asset( - 'assets/images/user.svg', - width: 30, - height: 30, - ), - ), - ), - title: Text(className, style: CustomTheme.textXl), - subtitle: Text(firstName, style: CustomTheme.text), - ), - ), - ); - } -} diff --git a/frontend/app_student/lib/routes.dart b/frontend/app_student/lib/routes.dart index 7daa713..c44cf9a 100644 --- a/frontend/app_student/lib/routes.dart +++ b/frontend/app_student/lib/routes.dart @@ -5,14 +5,14 @@ import 'package:app_student/api/users/repositories/user_repository.dart'; import 'package:app_student/api/week_schedule/repositories/week_schedule_repository.dart'; import 'package:app_student/class_groups/cubit/class_group_cubit.dart'; import 'package:app_student/config/config.dart'; -import 'package:app_student/profils/views/profil.dart'; +import 'package:app_student/profil/views/profil.dart'; +import 'package:app_student/school_space/views/school_space.dart'; import 'package:app_student/users/cubit/user_cubit.dart'; import 'package:app_student/week_schedule/cubit/week_schedule_cubit.dart'; import 'package:app_student/week_schedule/views/week_schedule.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; - import 'class_groups/views/class_group.dart'; import 'login/cubit/login_cubit.dart'; import 'login/views/login.dart'; @@ -22,6 +22,7 @@ class AppRoutes { static const loginPage = '/login'; static const schedulePage = '/schedule'; static const profilPage = '/profil'; + static const accountPage = '/account'; static final routes = [ GoRoute( @@ -34,7 +35,10 @@ class AppRoutes { create: (context) => ClassGroupRepository( apiService: ApiService(apiUrl: context.read().apiUrl))), - RepositoryProvider(create: (context) => UserRepository()), + RepositoryProvider( + create: (context) => UserRepository( + apiService: + ApiService(apiUrl: context.read().apiUrl))), ], child: MultiBlocProvider(providers: [ BlocProvider( @@ -57,7 +61,10 @@ class AppRoutes { key: state.pageKey, child: MultiRepositoryProvider( providers: [ - RepositoryProvider(create: (context) => UserRepository()), + RepositoryProvider( + create: (context) => UserRepository( + apiService: + ApiService(apiUrl: context.read().apiUrl))), ], child: MultiBlocProvider( providers: [ @@ -85,7 +92,10 @@ class AppRoutes { create: (context) => WeekScheduleRepository( apiService: ApiService(apiUrl: context.read().apiUrl))), - RepositoryProvider(create: (context) => UserRepository()), + RepositoryProvider( + create: (context) => UserRepository( + apiService: + ApiService(apiUrl: context.read().apiUrl))), ], child: MultiBlocProvider( providers: [ @@ -112,7 +122,10 @@ class AppRoutes { key: state.pageKey, child: MultiRepositoryProvider( providers: [ - RepositoryProvider(create: (context) => UserRepository()), + RepositoryProvider( + create: (context) => UserRepository( + apiService: + ApiService(apiUrl: context.read().apiUrl))), ], child: MultiBlocProvider( providers: [ @@ -127,5 +140,28 @@ class AppRoutes { ), ), ), + GoRoute( + path: accountPage, + pageBuilder: (context, state) => MaterialPage( + key: state.pageKey, + child: MultiRepositoryProvider( + providers: [ + RepositoryProvider( + create: (context) => UserRepository( + apiService: + ApiService(apiUrl: context.read().apiUrl))), + ], + child: MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => + UserCubit(userRepository: context.read()), + ), + ], + child: const SchoolSpacePage(), + ), + ), + ), + ), ]; } diff --git a/frontend/app_student/lib/school_space/views/forms/link_account_form.dart b/frontend/app_student/lib/school_space/views/forms/link_account_form.dart new file mode 100644 index 0000000..842762d --- /dev/null +++ b/frontend/app_student/lib/school_space/views/forms/link_account_form.dart @@ -0,0 +1,94 @@ +import 'package:app_student/shared_components/header_logo.dart'; +import 'package:app_student/shared_components/header_title.dart'; +import 'package:app_student/users/cubit/user_cubit.dart'; +import 'package:app_student/utils/custom_button.dart'; +import 'package:app_student/utils/custom_layout.dart'; +import 'package:app_student/utils/global.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +class LinkAccountForm extends StatelessWidget { + final UserCubit userCubit; + + const LinkAccountForm({super.key, required this.userCubit}); + + @override + Widget build(BuildContext context) { + final formKey = GlobalKey(); + String ine = ''; + String birthDate = ''; + final birthDateController = TextEditingController(); + + return CustomLayout( + appBar: HeaderLogo(appBarHeight: Global.screenHeight * 0.3), + body: Column( + children: [ + const HeaderTitle('Connexion'), + Form( + key: formKey, + child: Column( + children: [ + TextFormField( + decoration: const InputDecoration(labelText: 'INE'), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter your INE'; + } + return null; + }, + onSaved: (value) { + ine = value!; + }, + ), + TextFormField( + decoration: InputDecoration( + labelText: 'Birth Date', + suffixIcon: IconButton( + icon: const Icon(Icons.calendar_today), + onPressed: () async { + final DateTime? pickedDate = await showDatePicker( + context: context, + initialDate: DateTime.now(), + firstDate: DateTime(1900), + lastDate: DateTime.now(), + ); + if (pickedDate != null) { + birthDate = + DateFormat('dd/MM/yyyy').format(pickedDate); + birthDateController.text = birthDate; + formKey.currentState!.validate(); + } + }, + ), + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter your birth date'; + } + return null; + }, + onSaved: (value) { + birthDate = value!; + }, + controller: birthDateController, + ), + ], + ), + ), + ], + ), + bottomContent: CustomButton( + text: 'Valider', + onPressed: () { + if (formKey.currentState!.validate()) { + formKey.currentState!.save(); + userCubit.loginAndSaveId(ine, birthDate); + Navigator.of(context).pop(); + } + }, + ), + // Add your bottomBar here if you have one + // bottomBar: YourBottomBar(), + ); + } +} diff --git a/frontend/app_student/lib/school_space/views/school_space.dart b/frontend/app_student/lib/school_space/views/school_space.dart new file mode 100644 index 0000000..bfb4da3 --- /dev/null +++ b/frontend/app_student/lib/school_space/views/school_space.dart @@ -0,0 +1,113 @@ +import 'package:app_student/users/cubit/user_cubit.dart'; +import 'package:app_student/utils/custom_button.dart'; +import 'package:app_student/utils/custom_theme.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import '../../menu/menu_view.dart'; +import '../../shared_components/header_logo.dart'; +import '../../shared_components/header_title.dart'; +import '../../utils/custom_layout.dart'; +import '../../utils/global.dart'; +import 'forms/link_account_form.dart'; +import 'widgets/pdf_card.dart'; + +class SchoolSpacePage extends StatefulWidget { + const SchoolSpacePage({super.key}); + + @override + SchoolSpacePageState createState() => SchoolSpacePageState(); +} + +class SchoolSpacePageState extends State { + late final UserCubit userCubit; + + @override + void initState() { + super.initState(); + userCubit = context.read(); + } + + @override + Widget build(BuildContext context) { + return CustomLayout( + appBar: HeaderLogo(appBarHeight: Global.screenHeight * 0.3), + body: BlocBuilder( + builder: (context, state) { + if (state is UserLoading) { + return const Center(child: CircularProgressIndicator()); + } else if (state is UserLoggedIn) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + HeaderTitle(AppLocalizations.of(context)!.my3il), + Container( + padding: const EdgeInsets.all(10.0), + decoration: BoxDecoration( + color: CustomTheme.primaryColorLight.withOpacity(0.1), + borderRadius: BorderRadius.circular(10.0), + ), + child: const Text( + 'Votre espace est relié ! Vous pouvez désormais consulter vos notes et absences :).', + style: TextStyle( + fontSize: 18.0, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(height: 40), + PdfCard( + filePath: state.user.marksFile!.path, + title: 'Relevé de notes'), + const SizedBox(height: 20), + PdfCard( + filePath: state.user.absencesFile!.path, + title: "Relevé d'absences"), + ], + ); + } else if (state is UserInitial) { + return Column( + children: [ + const Padding( + padding: EdgeInsets.only(left: 10.0), + child: HeaderTitle('My3iL'), + ), + Container( + padding: const EdgeInsets.all(10.0), + decoration: BoxDecoration( + color: CustomTheme.primaryColorLight.withOpacity(0.1), + borderRadius: BorderRadius.circular(10.0), + ), + child: const Text( + 'Cet espace vous permet de relier votre compte Exnet 3il à l\'application pour consulter vos notes et absences.', + style: TextStyle( + fontSize: 18.0, + fontWeight: FontWeight.bold, + ), + ), + ), + SizedBox(height: Global.screenHeight * 0.2), + CustomButton( + text: 'Relier mon compte', + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + LinkAccountForm(userCubit: userCubit), + ), + ); + }, + ), + ], + ); + } else { + return const SizedBox.shrink(); + } + }, + ), + bottomBar: const MenuBarView(), + ); + } +} diff --git a/frontend/app_student/lib/school_space/views/widgets/pdf_card.dart b/frontend/app_student/lib/school_space/views/widgets/pdf_card.dart new file mode 100644 index 0000000..6e5689d --- /dev/null +++ b/frontend/app_student/lib/school_space/views/widgets/pdf_card.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:app_student/utils/custom_theme.dart'; + +import 'pdf_view.dart'; + +class PdfCard extends StatelessWidget { + final String filePath; + final String title; + + const PdfCard({super.key, required this.filePath, required this.title}); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + color: CustomTheme.primaryColor, + borderRadius: BorderRadius.circular(10.0), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.2), + spreadRadius: 1, + blurRadius: 2, + offset: const Offset(0, 1), + ), + ], + ), + child: ListTile( + contentPadding: const EdgeInsets.all(10.0), + leading: const Icon(Icons.picture_as_pdf, color: Colors.white), + title: Text( + title, + style: const TextStyle(color: Colors.white), + ), + trailing: const Icon(Icons.arrow_forward_ios, color: Colors.white), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => PdfViewPage(filePath: filePath), + ), + ); + }, + ), + ); + } +} diff --git a/frontend/app_student/lib/school_space/views/widgets/pdf_view.dart b/frontend/app_student/lib/school_space/views/widgets/pdf_view.dart new file mode 100644 index 0000000..d6c873b --- /dev/null +++ b/frontend/app_student/lib/school_space/views/widgets/pdf_view.dart @@ -0,0 +1,27 @@ +import 'package:app_student/utils/custom_layout.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_pdfview/flutter_pdfview.dart'; + +class PdfViewPage extends StatelessWidget { + final String filePath; + + const PdfViewPage({super.key, required this.filePath}); + + @override + Widget build(BuildContext context) { + return CustomLayout( + appBar: AppBar( + title: const Text('PDF View'), + actions: [ + IconButton( + icon: const Icon(Icons.download), + onPressed: () { + // Implement your download functionality here + }, + ), + ], + ), + body: PDFView(filePath: filePath), + ); + } +} diff --git a/frontend/app_student/lib/shared_components/app_bar.dart b/frontend/app_student/lib/shared_components/app_bar.dart index d62aa88..908c5e3 100644 --- a/frontend/app_student/lib/shared_components/app_bar.dart +++ b/frontend/app_student/lib/shared_components/app_bar.dart @@ -16,7 +16,9 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { final userState = context.watch().state; String className = ''; if (userState is UserLoaded) { - className = userState.user.className ?? ''; + className = (userState).user.className ?? ''; + } else if (userState is UserNameLoaded) { + className = (userState).user.className ?? ''; } return AppBar( backgroundColor: CustomTheme.primaryColor, diff --git a/frontend/app_student/lib/shared_components/header_logo.dart b/frontend/app_student/lib/shared_components/header_logo.dart index 4ea8662..ee38737 100644 --- a/frontend/app_student/lib/shared_components/header_logo.dart +++ b/frontend/app_student/lib/shared_components/header_logo.dart @@ -3,13 +3,21 @@ import 'package:flutter/material.dart'; class HeaderLogo extends StatelessWidget implements PreferredSizeWidget { final double appBarHeight; + final bool showBackButton; - const HeaderLogo({super.key, required this.appBarHeight}); + const HeaderLogo( + {super.key, required this.appBarHeight, this.showBackButton = false}); @override Widget build(BuildContext context) { return AppBar( backgroundColor: CustomTheme.primaryColor, + leading: showBackButton + ? IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.white), + onPressed: () => Navigator.of(context).pop(), + ) + : null, flexibleSpace: Center( child: SizedBox( width: 200.0, diff --git a/frontend/app_student/lib/users/cubit/user_cubit.dart b/frontend/app_student/lib/users/cubit/user_cubit.dart index 3350ddb..d1f3c30 100644 --- a/frontend/app_student/lib/users/cubit/user_cubit.dart +++ b/frontend/app_student/lib/users/cubit/user_cubit.dart @@ -1,9 +1,12 @@ +import 'dart:io'; + import 'package:app_student/api/class_groups/models/class_group_model.dart'; import 'package:app_student/api/users/models/user_model.dart'; import 'package:bloc/bloc.dart'; -import 'package:meta/meta.dart'; +import 'package:flutter/foundation.dart'; import '../../api/users/repositories/user_repository.dart'; +import '../../utils/global.dart'; part 'user_state.dart'; @@ -16,9 +19,23 @@ class UserCubit extends Cubit { try { emit(UserLoading()); final user = await userRepository.getUser(); - emit(UserLoaded(user)); + + if (user.className == null || user.className!.isEmpty) { + emit(UserWithoutClass(user)); + } else if (user.ine != null && + user.ine!.isNotEmpty && + user.birthDate != null && + user.studentId != null) { + emit(UserLoaded(user)); + } else if (user.ine == null || + (user.ine != null && user.ine!.isEmpty) || + user.birthDate == null) { + emit(UserNameLoaded(user)); + } } catch (e) { - emit(UserError(e.toString())); + if (!isClosed) { + emit(UserError(e.toString())); + } } } @@ -38,4 +55,50 @@ class UserCubit extends Cubit { Future clearUserClass() async { await userRepository.clearClass(); } + + Future loginAndSaveId(String username, String password) async { + emit(UserLoading()); + try { + final studentId = await userRepository.login(username, password); + Global.setIne(username); + Global.setBirthDate(password); + Global.setStudentId(studentId); + final user = await userRepository.getUser(); + emit(UserLoggedIn(user)); + } catch (e) { + emit(UserError(e.toString())); + } + } + + Future fetchMarks() async { + emit(UserLoading()); + try { + final user = await userRepository.getUser(); + if (user.studentId != null) { + final File marks = await userRepository.getMarks(user.studentId!); + if (kDebugMode) { + print(marks.path); + } + final File absences = await userRepository.getAbsences(user.studentId!); + if (kDebugMode) { + print(absences.path); + } + emit(UserLoaded(user)); + } else { + throw Exception('No student ID found in SharedPreferences'); + } + } catch (e) { + emit(UserError(e.toString())); + } + } + + Future checkStudentId() async { + final user = await getCurrentUser(); + + if (user.studentId != null) { + emit(UserLoggedIn(user)); + } else { + emit(UserInitial()); + } + } } diff --git a/frontend/app_student/lib/users/cubit/user_state.dart b/frontend/app_student/lib/users/cubit/user_state.dart index c08adbb..905c622 100644 --- a/frontend/app_student/lib/users/cubit/user_state.dart +++ b/frontend/app_student/lib/users/cubit/user_state.dart @@ -13,10 +13,28 @@ class UserLoaded extends UserState { UserLoaded(this.user); } +class UserNameLoaded extends UserState { + final UserModel user; + + UserNameLoaded(this.user); +} + class UserClassesSelected extends UserState {} +class UserWithoutClass extends UserState { + final UserModel user; + + UserWithoutClass(this.user); +} + class UserError extends UserState { final String message; UserError(this.message); } + +class UserLoggedIn extends UserState { + final UserModel user; + + UserLoggedIn(this.user); +} diff --git a/frontend/app_student/lib/utils/custom_button.dart b/frontend/app_student/lib/utils/custom_button.dart new file mode 100644 index 0000000..e279555 --- /dev/null +++ b/frontend/app_student/lib/utils/custom_button.dart @@ -0,0 +1,41 @@ +import 'package:app_student/utils/custom_theme.dart'; +import 'package:flutter/material.dart'; + +class CustomButton extends StatelessWidget { + final String text; + final VoidCallback onPressed; + final Color backgroundColor; + final Color textColor; + + const CustomButton({ + required this.text, + required this.onPressed, + this.backgroundColor = CustomTheme.secondaryColor, + this.textColor = Colors.white, + super.key, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: double.infinity, + height: 50.0, + child: ElevatedButton( + style: ButtonStyle( + textStyle: MaterialStateProperty.all( + CustomTheme.text.toBold, + ), + backgroundColor: MaterialStateProperty.all(backgroundColor), + foregroundColor: MaterialStateProperty.all(textColor), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(3.0), + ), + ), + ), + onPressed: onPressed, + child: Text(text, style: CustomTheme.textSmall.toColorWhite), + ), + ); + } +} diff --git a/frontend/app_student/lib/utils/custom_layout.dart b/frontend/app_student/lib/utils/custom_layout.dart index 0b3e841..456281e 100644 --- a/frontend/app_student/lib/utils/custom_layout.dart +++ b/frontend/app_student/lib/utils/custom_layout.dart @@ -23,17 +23,25 @@ class CustomLayout extends StatelessWidget { padding: const EdgeInsets.only(left: 20.0, right: 20.0), child: LayoutBuilder( builder: (context, constraints) { - return Column( + return Stack( children: [ - const SizedBox(height: 10), - Expanded( - child: body, + SingleChildScrollView( + child: Column( + children: [ + const SizedBox(height: 10), + body, + if (bottomContent != null) + SizedBox( + height: MediaQuery.of(context).size.height * 0.3), + ], + ), ), if (bottomContent != null) - SizedBox( - height: constraints.maxHeight * 0.2, - // 20% of screen height - child: bottomContent, + Positioned( + left: 0, + right: 0, + bottom: 10, + child: bottomContent!, ), ], ); diff --git a/frontend/app_student/lib/utils/custom_theme.dart b/frontend/app_student/lib/utils/custom_theme.dart index 7de4629..2e61504 100644 --- a/frontend/app_student/lib/utils/custom_theme.dart +++ b/frontend/app_student/lib/utils/custom_theme.dart @@ -13,6 +13,18 @@ class CustomTheme { static const TextStyle text = TextStyle(fontSize: 16); static const TextStyle textSmall = TextStyle(fontSize: 14); static const TextStyle textXs = TextStyle(fontSize: 12); + + static ThemeData get theme { + return ThemeData( + appBarTheme: const AppBarTheme( + iconTheme: IconThemeData(color: Colors.white), + ), + focusColor: secondaryColor, + primaryColor: primaryColor, + secondaryHeaderColor: primaryColor, + fontFamily: 'Arial', + ); + } } extension TextStyleHelpers on TextStyle { diff --git a/frontend/app_student/lib/utils/global.dart b/frontend/app_student/lib/utils/global.dart index ea271ab..5661e67 100644 --- a/frontend/app_student/lib/utils/global.dart +++ b/frontend/app_student/lib/utils/global.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; class Global { static late double screenWidth; @@ -8,4 +9,38 @@ class Global { screenWidth = MediaQuery.of(context).size.width; screenHeight = MediaQuery.of(context).size.height; } + + static Future get prefs async { + return await SharedPreferences.getInstance(); + } + + static Future get studentId async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getInt('studentId'); + } + + static Future setStudentId(int value) async { + final prefs = await SharedPreferences.getInstance(); + prefs.setInt('studentId', value); + } + + static Future get ine async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString('ine') ?? ''; + } + + static Future setIne(String value) async { + final prefs = await SharedPreferences.getInstance(); + prefs.setString('ine', value); + } + + static Future get birthDate async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString('birthDate') ?? ''; + } + + static Future setBirthDate(String value) async { + final prefs = await SharedPreferences.getInstance(); + prefs.setString('birthDate', value); + } } diff --git a/frontend/app_student/macos/Flutter/GeneratedPluginRegistrant.swift b/frontend/app_student/macos/Flutter/GeneratedPluginRegistrant.swift index 724bb2a..b8e2b22 100644 --- a/frontend/app_student/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/frontend/app_student/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,8 +5,10 @@ import FlutterMacOS import Foundation +import path_provider_foundation import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/frontend/app_student/pubspec.lock b/frontend/app_student/pubspec.lock index 939c271..69c35ad 100644 --- a/frontend/app_student/pubspec.lock +++ b/frontend/app_student/pubspec.lock @@ -123,6 +123,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_pdfview: + dependency: "direct main" + description: + name: flutter_pdfview + sha256: a9055bf920c7095bf08c2781db431ba23577aa5da5a056a7152dc89a18fbec6f + url: "https://pub.dev" + source: hosted + version: "1.3.2" flutter_svg: dependency: "direct main" description: @@ -269,6 +277,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 + url: "https://pub.dev" + source: hosted + version: "2.1.3" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d + url: "https://pub.dev" + source: hosted + version: "2.2.4" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + url: "https://pub.dev" + source: hosted + version: "2.3.2" path_provider_linux: dependency: transitive description: diff --git a/frontend/app_student/pubspec.yaml b/frontend/app_student/pubspec.yaml index 1508b86..4293507 100644 --- a/frontend/app_student/pubspec.yaml +++ b/frontend/app_student/pubspec.yaml @@ -48,6 +48,8 @@ dependencies: flutter_localizations: sdk: flutter fluttertoast: ^8.2.4 + path_provider: ^2.1.3 + flutter_pdfview: ^1.3.2 dev_dependencies: