diff --git a/.github/workflows/deploy-backend-dev.yml b/.github/workflows/deploy-backend-dev.yml index a723df7..87a8333 100644 --- a/.github/workflows/deploy-backend-dev.yml +++ b/.github/workflows/deploy-backend-dev.yml @@ -22,8 +22,7 @@ jobs: ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - name: Upload PHP code - run: scp -o StrictHostKeyChecking=no -r * ${{ secrets.SERVER_USERNAME }}@${{ secrets.SERVER_HOST }}:/home/julesartd/www/api-dev-edt-3il - + run: scp -o StrictHostKeyChecking=no -r backend ${{ secrets.SERVER_USERNAME }}@${{ secrets.SERVER_HOST }}:/home/julesartd/www/api-dev-edt-3il install_dependencies: needs: deploy diff --git a/frontend/app_student/.env.dist b/frontend/app_student/.env.dist new file mode 100644 index 0000000..4873f58 --- /dev/null +++ b/frontend/app_student/.env.dist @@ -0,0 +1,2 @@ +DEV_API_URL= +PROD_API_URL= \ No newline at end of file diff --git a/frontend/app_student/.gitignore b/frontend/app_student/.gitignore index 4dfa933..8049450 100644 --- a/frontend/app_student/.gitignore +++ b/frontend/app_student/.gitignore @@ -43,4 +43,8 @@ app.*.map.json /android/app/release # FVM Version Cache -.fvm/ \ No newline at end of file +.fvm/ + +# Env file +/.env +/.env.local \ No newline at end of file diff --git a/frontend/app_student/README.md b/frontend/app_student/README.md index f8c8ee2..9164882 100644 --- a/frontend/app_student/README.md +++ b/frontend/app_student/README.md @@ -1,16 +1,43 @@ -# app_student +# Application Flutter pour l'emploi du temps et les notes - Groupe 3iL -A new Flutter project. +Ce projet est une application mobile développée en Flutter par des étudiants du Groupe 3iL. Elle vise à fournir aux étudiants un accès facile à leur emploi du temps et à leurs notes. -## Getting Started +## Configuration -This project is a starting point for a Flutter application. +### Environnements -A few resources to get you started if this is your first Flutter project: +L'application est configurée pour fonctionner dans deux environnements différents : développement (dev) et production (prod). Chaque environnement utilise une API distincte. -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) +- **Dev**: Utilise une API de développement pour tester et déboguer l'application. +- **Prod**: Utilise une API de production pour une utilisation en direct. -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +## Version de Flutter + +Ce projet utilise Flutter version 3.19.2. + +## Internationalisation (i18n) + +L'application utilise la fonctionnalité de localisation (l10n) de Flutter pour supporter plusieurs langues. N'oubliez pas de générer les fichiers de localisation en exécutant `flutter gen-l10n` avant de construire l'application. + +## Pré-requis + +Avant de pousser votre code, assurez-vous de respecter les points suivants : + +- Utilisez `dart format` pour formater votre code. +- Exécutez `flutter analyze` pour détecter tout problème dans votre code. +- Assurez-vous d'exécuter `pub get` pour installer toutes les dépendances du projet. + +### Variables d'environnement + +Pour que l'application fonctionne correctement, vous devez définir les variables d'environnement suivantes dans un fichier `.env` à la racine du projet : + +- `API_URL_DEV`: URL de l'API de développement. +- `API_URL_PROD`: URL de l'API de production. + +## Contributions + +Les contributions des autres étudiants sont les bienvenues! N'hésitez pas à proposer des améliorations, des corrections de bugs ou de nouvelles fonctionnalités en soumettant des pull requests. + +## Licence + +Ce projet est destiné à des fins éducatives. diff --git a/frontend/app_student/assets/images/student-info.svg b/frontend/app_student/assets/images/student-info.svg new file mode 100644 index 0000000..6ac60ae --- /dev/null +++ b/frontend/app_student/assets/images/student-info.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/app_student/assets/images/user.svg b/frontend/app_student/assets/images/user.svg new file mode 100644 index 0000000..30cbd7d --- /dev/null +++ b/frontend/app_student/assets/images/user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/app_student/l10n.yaml b/frontend/app_student/l10n.yaml new file mode 100644 index 0000000..35d81e9 --- /dev/null +++ b/frontend/app_student/l10n.yaml @@ -0,0 +1,3 @@ +arb-dir: lib/l10n +template-arb-file: app_fr.arb +output-localization-file: app_localizations.dart \ No newline at end of file diff --git a/frontend/app_student/lib/config/dev_config.dart b/frontend/app_student/lib/config/dev_config.dart index af2b236..0766185 100644 --- a/frontend/app_student/lib/config/dev_config.dart +++ b/frontend/app_student/lib/config/dev_config.dart @@ -1,6 +1,8 @@ +import 'package:flutter_dotenv/flutter_dotenv.dart'; + import 'config.dart'; class DevConfig extends Config { @override - String get apiUrl => 'https://api-dev.lukasvalois.com'; + String get apiUrl => dotenv.env['DEV_API_URL'] ?? 'http://localhost:8000'; } diff --git a/frontend/app_student/lib/config/prod_config.dart b/frontend/app_student/lib/config/prod_config.dart index 31e87e0..fbf9831 100644 --- a/frontend/app_student/lib/config/prod_config.dart +++ b/frontend/app_student/lib/config/prod_config.dart @@ -1,6 +1,8 @@ +import 'package:flutter_dotenv/flutter_dotenv.dart'; + import 'config.dart'; class ProdConfig extends Config { @override - String get apiUrl => 'https://api.lukasvalois.com'; + String get apiUrl => dotenv.env['PROD_API_URL'] ?? 'http://localhost:8000'; } diff --git a/frontend/app_student/lib/l10n/app_fr.arb b/frontend/app_student/lib/l10n/app_fr.arb new file mode 100644 index 0000000..57e0c37 --- /dev/null +++ b/frontend/app_student/lib/l10n/app_fr.arb @@ -0,0 +1,72 @@ +{ + "error404": "Page non trouvée {pageUri}", + "@error404": { + "description": "Message d'erreur pour une page non trouvée", + "placeholders": { + "pageUri": { + "type": "String", + "example": "/login" + } + } + }, + "genericError": "Une erreur est survenue", + "@genericError": { + "description": "Message d'erreur générique", + "placeholders": { + "error": { + "type": "String", + "example": "Une erreur est survenue" + } + } + }, + "loginButton": "Connexion", + "@loginButton": { + "description": "Texte du bouton de connexion" + }, + "loginFirstNameLabel": "Prénom", + "@loginFirstName": { + "description": "Texte du label pour le prénom" + }, + "loginFirstNameHint": "Entrez votre prénom", + "@loginFirstNameHint": { + "description": "Texte de l'attribut placeholder pour le prénom" + }, + "loginIneLabel": "INE", + "@loginIneLabel": { + "description": "Texte du label pour le numéro INE" + }, + "loginIneHint": "Entrez votre numéro INE", + "@loginIneHint": { + "description": "Texte de l'attribut placeholder pour le numéro INE" + }, + "loginBirthDateLabel": "Date de naissance", + "@loginBirthDateLabel": { + "description": "Texte du label pour la date de naissance" + }, + "loginWelcomeTitle": "Bonjour :)", + "@loginWelcomeTitle": { + "description": "Texte d'accueil de la page de connexion" + }, + "loginWelcomeTitleError": "Oops, ca va aller", + "@loginWelcomeTitleError": { + "description": "Texte d'erreur d'accueil de la page de connexion" + }, + "loginFieldError": "Veuillez remplir tous les champs", + "@loginFieldError": { + "description": "Message d'erreur pour les champs de connexion" + }, + "classSelectionTitle": "Salut {firstName} !", + "@classSelectionTitle": { + "description": "Texte d'accueil de la page de sélection de classe", + "placeholders": { + "firstName": { + "type": "String", + "example": "John" + } + } + }, + "classSelectionSubtitle": "Sélectionne ta classe", + "@classSelectionSubtitle": { + "description": "Texte de sous-titre de la page de sélection de classe" + } +} \ No newline at end of file diff --git a/frontend/app_student/lib/login/views/login_page.dart b/frontend/app_student/lib/login/views/login_page.dart index 9b7a0e9..a2756fe 100644 --- a/frontend/app_student/lib/login/views/login_page.dart +++ b/frontend/app_student/lib/login/views/login_page.dart @@ -3,6 +3,7 @@ import 'package:app_student/login/widgets/form/form_login.dart'; import 'package:app_student/login/widgets/header/header_text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:go_router/go_router.dart'; import '../widgets/header/header_logo.dart'; @@ -20,12 +21,12 @@ class LoginPage extends StatelessWidget { }); return Container(); } else if (state is LoginInitial) { - return const Scaffold( + return Scaffold( body: Column( children: [ - HeaderLogo(), - HeaderText('Bonjour :)'), - Expanded(child: FormLogin()), + const HeaderLogo(), + HeaderText(AppLocalizations.of(context)!.loginWelcomeTitle), + const Expanded(child: FormLogin()), ], ), ); @@ -37,18 +38,19 @@ class LoginPage extends StatelessWidget { } else if (state is LoginFieldError) { WidgetsBinding.instance.addPostFrameCallback((_) { ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Veuillez remplir tous les champs'), + SnackBar( + content: Text(AppLocalizations.of(context)!.loginFieldError), backgroundColor: Colors.red, ), ); }); - return const Scaffold( + return Scaffold( body: Column( children: [ - HeaderLogo(), - HeaderText('Bonjour :)'), - Expanded(child: FormLogin()), + const HeaderLogo(), + HeaderText( + AppLocalizations.of(context)!.loginWelcomeTitleError), + const Expanded(child: FormLogin()), ], ), ); diff --git a/frontend/app_student/lib/login/widgets/form/button_submit.dart b/frontend/app_student/lib/login/widgets/form/button_submit.dart index 76693db..d0ed049 100644 --- a/frontend/app_student/lib/login/widgets/form/button_submit.dart +++ b/frontend/app_student/lib/login/widgets/form/button_submit.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import '../../cubit/login_cubit.dart'; @@ -52,7 +53,9 @@ class SubmitButton extends StatelessWidget { .read() .saveLoginDetails(ine, name, birthDate); }, - child: const Text('Connexion'), + child: Text( + AppLocalizations.of(context)!.loginButton, + ), ), ), ], diff --git a/frontend/app_student/lib/login/widgets/form/inputs/input_birthdate.dart b/frontend/app_student/lib/login/widgets/form/inputs/input_birthdate.dart index 5be7080..0491895 100644 --- a/frontend/app_student/lib/login/widgets/form/inputs/input_birthdate.dart +++ b/frontend/app_student/lib/login/widgets/form/inputs/input_birthdate.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class BirthDateField extends StatefulWidget { final TextEditingController controller; @@ -42,7 +43,7 @@ class BirthDateFieldState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Date de naissance', + AppLocalizations.of(context)!.loginBirthDateLabel, style: TextStyle( fontSize: 12.0, color: Colors.grey[600], diff --git a/frontend/app_student/lib/login/widgets/form/inputs/input_ine.dart b/frontend/app_student/lib/login/widgets/form/inputs/input_ine.dart index 775c7e7..8ca5cd1 100644 --- a/frontend/app_student/lib/login/widgets/form/inputs/input_ine.dart +++ b/frontend/app_student/lib/login/widgets/form/inputs/input_ine.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class INETextField extends StatelessWidget { final TextEditingController controller; @@ -16,7 +17,7 @@ class INETextField extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'INE', + AppLocalizations.of(context)!.loginIneLabel, style: TextStyle( fontSize: 12.0, color: Colors.grey[600], @@ -26,7 +27,7 @@ class INETextField extends StatelessWidget { TextFormField( controller: controller, decoration: InputDecoration( - hintText: 'Numéro INE', + hintText: AppLocalizations.of(context)!.loginIneHint, contentPadding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0), border: OutlineInputBorder( diff --git a/frontend/app_student/lib/login/widgets/form/inputs/input_prenom.dart b/frontend/app_student/lib/login/widgets/form/inputs/input_prenom.dart index d3aba1a..16e19b2 100644 --- a/frontend/app_student/lib/login/widgets/form/inputs/input_prenom.dart +++ b/frontend/app_student/lib/login/widgets/form/inputs/input_prenom.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class FirstnameTextField extends StatelessWidget { final TextEditingController controller; @@ -13,7 +14,7 @@ class FirstnameTextField extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Prénom', + AppLocalizations.of(context)!.loginFirstNameLabel, style: TextStyle( fontSize: 12.0, color: Colors.grey[600], @@ -26,7 +27,7 @@ class FirstnameTextField extends StatelessWidget { color: Colors.grey[600], ), decoration: InputDecoration( - hintText: 'Entrez votre prénom ici', + hintText: AppLocalizations.of(context)!.loginFirstNameHint, contentPadding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0), border: OutlineInputBorder( diff --git a/frontend/app_student/lib/login/widgets/header/header_logo.dart b/frontend/app_student/lib/login/widgets/header/header_logo.dart index 430dd84..0c1ff84 100644 --- a/frontend/app_student/lib/login/widgets/header/header_logo.dart +++ b/frontend/app_student/lib/login/widgets/header/header_logo.dart @@ -10,8 +10,8 @@ class HeaderLogo extends StatelessWidget { color: const Color(0xFF005067), child: Center( child: SizedBox( - width: 200.0, // Largeur de l'image - height: 200.0, // Hauteur de l'image + width: 200.0, + height: 200.0, child: Image.asset('assets/images/3il-logo.jpg'), ), ), diff --git a/frontend/app_student/lib/main_dev.dart b/frontend/app_student/lib/main_dev.dart index f1cbdda..9ba04b1 100644 --- a/frontend/app_student/lib/main_dev.dart +++ b/frontend/app_student/lib/main_dev.dart @@ -1,13 +1,18 @@ import 'package:app_student/config/dev_config.dart'; import 'package:app_student/routes.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/date_symbol_data_local.dart'; import 'package:provider/provider.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'config/config.dart'; -void main() { +void main() async { + await dotenv.load(); + initializeDateFormatting('fr_FR', null).then((_) { runApp( Provider( @@ -30,7 +35,8 @@ class MyApp extends StatelessWidget { return MaterialPage( child: Scaffold( body: Center( - child: Text('Page not found: ${state.uri}'), + child: Text( + AppLocalizations.of(context)!.error404(state.uri.toString())), ), ), ); @@ -45,6 +51,15 @@ class MyApp extends StatelessWidget { focusColor: const Color(0xffE84E0F), fontFamily: 'Arial', ), + localizationsDelegates: const [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: const [ + Locale('fr'), + ], routerConfig: router, ); } diff --git a/frontend/app_student/lib/main_prod.dart b/frontend/app_student/lib/main_prod.dart index 7982f2b..d8d39ad 100644 --- a/frontend/app_student/lib/main_prod.dart +++ b/frontend/app_student/lib/main_prod.dart @@ -1,5 +1,5 @@ import 'package:app_student/config/prod_config.dart'; -import 'package:app_student/login/views/login_page.dart'; +import 'package:app_student/profils/views/profil_page.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -25,7 +25,7 @@ class MyApp extends StatelessWidget { primarySwatch: Colors.blue, focusColor: const Color(0xffE84E0F), ), - home: const LoginPage(), + home: const ProfilPage(), ); } } diff --git a/frontend/app_student/lib/profils/views/profil_page.dart b/frontend/app_student/lib/profils/views/profil_page.dart new file mode 100644 index 0000000..5479b0b --- /dev/null +++ b/frontend/app_student/lib/profils/views/profil_page.dart @@ -0,0 +1,67 @@ +import 'package:app_student/class_groups/views/widgets/header/header_text.dart'; +import 'package:app_student/profils/views/widgets/class_group_button.dart'; +import 'package:flutter/material.dart'; +import 'package:app_student/login/widgets/header/header_logo.dart'; +import 'package:flutter_svg/svg.dart'; + +class ProfilPage extends StatelessWidget { + const ProfilPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Column( + children: [ + const HeaderLogo(), + const Padding( + padding: EdgeInsets.all(15.0), + child: HeaderText('Quel beau profil ####### !'), + ), + Padding( + padding: const EdgeInsets.all(30.0), + child: Card( + child: ListTile( + leading: SizedBox( + width: 50, + child: ColorFiltered( + colorFilter: const ColorFilter.mode( + Color(0xFF005067), BlendMode.srcIn), + child: SvgPicture.asset( + 'assets/images/user.svg', + width: 30, + height: 30, + ), + ), + ), + title: const Text('Classe'), + subtitle: const Text('Nom du user'), + ), + ), + ), + Padding( + padding: const EdgeInsets.all(30.0), + child: Card( + child: ListTile( + leading: SizedBox( + width: 50, + child: ColorFiltered( + colorFilter: const ColorFilter.mode( + Color(0xFF005067), BlendMode.srcIn), + child: SvgPicture.asset( + 'assets/images/student-info.svg', + width: 30, + height: 30, + ), + ), + ), + title: const Text('ISBN'), + subtitle: const Text('dd/mm/yyyy'), + ), + ), + ), + const ClassGroupButton(), + ], + ), + ); + } +} diff --git a/frontend/app_student/lib/profils/views/widgets/class_group_button.dart b/frontend/app_student/lib/profils/views/widgets/class_group_button.dart new file mode 100644 index 0000000..1646a31 --- /dev/null +++ b/frontend/app_student/lib/profils/views/widgets/class_group_button.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +class ClassGroupButton extends StatelessWidget { + const ClassGroupButton({super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(left: 25.0, top: 80.0, right: 25.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: double.infinity, + height: 50.0, + child: ElevatedButton( + style: ButtonStyle( + textStyle: MaterialStateProperty.all( + const TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + backgroundColor: MaterialStateProperty.all( + Theme.of(context).focusColor), + foregroundColor: MaterialStateProperty.all(Colors.white), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(3.0), + ), + ), + ), + onPressed: () { + GoRouter.of(context).go('/class-list'); + }, + child: const Text('Changer de classe'), + ), + ), + ], + ), + ); + } +} diff --git a/frontend/app_student/pubspec.lock b/frontend/app_student/pubspec.lock index 53d83f4..21eeab5 100644 --- a/frontend/app_student/pubspec.lock +++ b/frontend/app_student/pubspec.lock @@ -102,6 +102,14 @@ packages: url: "https://pub.dev" source: hosted version: "8.1.4" + flutter_dotenv: + dependency: "direct main" + description: + name: flutter_dotenv + sha256: "9357883bdd153ab78cbf9ffa07656e336b8bbb2b5a3ca596b0b27e119f7c7d77" + url: "https://pub.dev" + source: hosted + version: "5.1.0" flutter_lints: dependency: "direct dev" description: @@ -110,6 +118,11 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" flutter_svg: dependency: "direct main" description: @@ -156,10 +169,10 @@ packages: dependency: "direct main" description: name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" url: "https://pub.dev" source: hosted - version: "0.19.0" + version: "0.18.1" leak_tracker: dependency: transitive description: diff --git a/frontend/app_student/pubspec.yaml b/frontend/app_student/pubspec.yaml index b5bb6b0..66a657b 100644 --- a/frontend/app_student/pubspec.yaml +++ b/frontend/app_student/pubspec.yaml @@ -43,7 +43,11 @@ dependencies: meta: ^1.11.0 flutter_svg: ^2.0.10+1 go_router: ^13.2.0 - intl: ^0.19.0 + intl: ^0.18.1 + flutter_dotenv: ^5.1.0 + flutter_localizations: + sdk: flutter + dev_dependencies: flutter_test: @@ -61,6 +65,7 @@ dev_dependencies: # The following section is specific to Flutter packages. flutter: + generate: true # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in @@ -74,6 +79,7 @@ flutter: assets: - assets/images/ + - .env # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware @@ -103,5 +109,6 @@ flutter: fonts: - asset: assets/fonts/arial.ttf + # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages