From c6530985602918987f4af87aa5cf1365edcc6517 Mon Sep 17 00:00:00 2001 From: DahoudG Date: Tue, 27 Aug 2024 18:53:20 +0000 Subject: [PATCH] =?UTF-8?q?Le=20menu=20de=20la=20page=20principale=20apr?= =?UTF-8?q?=C3=A8s=20s'=C3=AAtre=20authentifi=C3=A9=20est=20ok?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/config/injection/injection.dart | 2 +- lib/config/router.dart | 10 +- lib/core/constants/colors.dart | 0 lib/core/utils/validators.dart | 21 +++ .../user_repository_impl.dart | 0 lib/main.dart | 2 + .../screens/event/event_screen.dart | 43 ++++- .../screens/home/home_screen.dart | 163 +++++++++++++++++- .../screens/login/login_screen.dart | 139 +++++++++++---- .../screens/story/story_screen.dart | 54 ++++++ .../state_management/event_bloc.dart | 0 .../state_management/payment_bloc.dart | 0 .../state_management/reservation_bloc.dart | 0 lib/presentation/widgets/custom_appbar.dart | 30 ++++ lib/presentation/widgets/custom_button.dart | 15 +- lib/presentation/widgets/custom_drawer.dart | 47 +++++ 16 files changed, 478 insertions(+), 48 deletions(-) create mode 100644 lib/core/constants/colors.dart create mode 100644 lib/core/utils/validators.dart rename lib/data/{datasources => repositories}/user_repository_impl.dart (100%) create mode 100644 lib/presentation/screens/story/story_screen.dart create mode 100644 lib/presentation/state_management/event_bloc.dart create mode 100644 lib/presentation/state_management/payment_bloc.dart create mode 100644 lib/presentation/state_management/reservation_bloc.dart create mode 100644 lib/presentation/widgets/custom_appbar.dart create mode 100644 lib/presentation/widgets/custom_drawer.dart diff --git a/lib/config/injection/injection.dart b/lib/config/injection/injection.dart index 271bc79..5197f95 100644 --- a/lib/config/injection/injection.dart +++ b/lib/config/injection/injection.dart @@ -2,7 +2,7 @@ import 'package:get_it/get_it.dart'; import 'package:http/http.dart' as http; import '../../data/datasources/user_remote_data_source.dart'; -import '../../data/datasources/user_repository_impl.dart'; +import '../../data/repositories/user_repository_impl.dart'; import '../../domain/usecases/get_user.dart'; final sl = GetIt.instance; diff --git a/lib/config/router.dart b/lib/config/router.dart index 8616d41..f9ed4b7 100644 --- a/lib/config/router.dart +++ b/lib/config/router.dart @@ -1,12 +1,20 @@ import 'package:flutter/material.dart'; import 'package:afterwork/presentation/screens/login/login_screen.dart'; +import 'package:afterwork/presentation/screens/home/home_screen.dart'; +import 'package:afterwork/presentation/screens/event/event_screen.dart'; +import 'package:afterwork/presentation/screens/story/story_screen.dart'; class AppRouter { static Route generateRoute(RouteSettings settings) { switch (settings.name) { case '/': return MaterialPageRoute(builder: (_) => const LoginScreen()); - // Ajoute d'autres routes ici + case '/home': + return MaterialPageRoute(builder: (_) => const HomeScreen()); + case '/event': + return MaterialPageRoute(builder: (_) => const EventScreen()); + case '/story': + return MaterialPageRoute(builder: (_) => const StoryScreen()); default: return MaterialPageRoute( builder: (_) => const Scaffold( diff --git a/lib/core/constants/colors.dart b/lib/core/constants/colors.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/core/utils/validators.dart b/lib/core/utils/validators.dart new file mode 100644 index 0000000..a229fbf --- /dev/null +++ b/lib/core/utils/validators.dart @@ -0,0 +1,21 @@ +class Validators { + static String? validateEmail(String? value) { + if (value == null || value.isEmpty) { + return 'Veuillez entrer votre email'; + } + if (!RegExp(r'^[^@]+@[^@]+\.[^@]+').hasMatch(value)) { + return 'Veuillez entrer un email valide'; + } + return null; + } + + static String? validatePassword(String? value) { + if (value == null || value.isEmpty) { + return 'Veuillez entrer votre mot de passe'; + } + if (value.length < 6) { + return 'Le mot de passe doit comporter au moins 6 caractères'; + } + return null; + } +} diff --git a/lib/data/datasources/user_repository_impl.dart b/lib/data/repositories/user_repository_impl.dart similarity index 100% rename from lib/data/datasources/user_repository_impl.dart rename to lib/data/repositories/user_repository_impl.dart diff --git a/lib/main.dart b/lib/main.dart index 379d61d..b0b7c1b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -14,6 +14,8 @@ class AfterWorkApp extends StatelessWidget { return MaterialApp( title: 'AfterWork', theme: AppTheme.lightTheme, + darkTheme: AppTheme.darkTheme, // Ajout du thème sombre + themeMode: ThemeMode.system, // Choix automatique du thème en fonction du système onGenerateRoute: AppRouter.generateRoute, initialRoute: '/', ); diff --git a/lib/presentation/screens/event/event_screen.dart b/lib/presentation/screens/event/event_screen.dart index ae876ad..f2f695c 100644 --- a/lib/presentation/screens/event/event_screen.dart +++ b/lib/presentation/screens/event/event_screen.dart @@ -7,12 +7,47 @@ class EventScreen extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('Événements'), + title: const Text('Créer un Événement'), + backgroundColor: Colors.blueAccent, ), - body: const Center( - child: Text('Liste des événements'), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Titre de l\'événement', + style: TextStyle(fontSize: 18, color: Colors.white), + ), + const SizedBox(height: 8), + TextField( + decoration: InputDecoration( + hintText: 'Entrez le titre de l\'événement', + filled: true, + fillColor: Colors.white.withOpacity(0.1), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10.0), + borderSide: BorderSide.none, + ), + hintStyle: const TextStyle(color: Colors.white70), + ), + style: const TextStyle(color: Colors.white), + ), + const SizedBox(height: 20), + ElevatedButton( + onPressed: () { + // Logique pour créer l'événement + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blueAccent, + padding: const EdgeInsets.all(16.0), + ), + child: const Text('Créer l\'événement'), + ), + ], + ), ), + backgroundColor: Colors.black, ); } } - diff --git a/lib/presentation/screens/home/home_screen.dart b/lib/presentation/screens/home/home_screen.dart index a86dfee..2762241 100644 --- a/lib/presentation/screens/home/home_screen.dart +++ b/lib/presentation/screens/home/home_screen.dart @@ -1,17 +1,172 @@ import 'package:flutter/material.dart'; -class HomeScreen extends StatelessWidget { +class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); + @override + _HomeScreenState createState() => _HomeScreenState(); +} + +class _HomeScreenState extends State with SingleTickerProviderStateMixin { + late TabController _tabController; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: 5, vsync: this); + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } + + void _onMenuSelected(BuildContext context, String option) { + // Implémente la logique pour chaque option ici + switch (option) { + case 'Publier': + // Redirige vers la page de publication + break; + case 'Story': + // Redirige vers la page de création de Story + break; + default: + break; + } + } + @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text('AfterWork'), + backgroundColor: Colors.black, // Fond noir pour l'AppBar + elevation: 0, // Enlève l'ombre sous l'AppBar + leading: Padding( + padding: const EdgeInsets.all(8.0), + child: Image.asset( + 'lib/assets/images/logo.png', // Chemin correct de ton logo + height: 40, + ), + ), + actions: [ + // Bouton + + CircleAvatar( + backgroundColor: Colors.white, // Cercle blanc + radius: 18, // Réduit la taille du bouton + child: PopupMenuButton( + onSelected: (value) => _onMenuSelected(context, value), + itemBuilder: (context) => [ + const PopupMenuItem( + value: 'Publier', + child: Text('Publier'), + ), + const PopupMenuItem( + value: 'Story', + child: Text('Story'), + ), + ], + icon: const Icon(Icons.add, color: Colors.blueAccent, size: 20), // Icône bleue légèrement plus petite + color: Colors.white, // Menu contextuel en blanc + ), + ), + const SizedBox(width: 8), // Réduit l'espacement entre les boutons + // Bouton Recherche + CircleAvatar( + backgroundColor: Colors.white, + radius: 18, // Réduit la taille du bouton + child: IconButton( + icon: const Icon(Icons.search, color: Colors.blueAccent, size: 20), // Icône bleue légèrement plus petite + onPressed: () { + // Implémente la logique de recherche ici + }, + ), + ), + const SizedBox(width: 8), // Réduit l'espacement entre les boutons + // Bouton Messagerie + CircleAvatar( + backgroundColor: Colors.white, + radius: 18, // Réduit la taille du bouton + child: IconButton( + icon: const Icon(Icons.message, color: Colors.blueAccent, size: 20), // Icône bleue légèrement plus petite + onPressed: () { + // Implémente la logique de messagerie ici + }, + ), + ), + const SizedBox(width: 8), // Réduit l'espacement entre les boutons + ], + bottom: TabBar( + controller: _tabController, + indicatorColor: Colors.blueAccent, + tabs: const [ + Tab(icon: Icon(Icons.home), text: 'Accueil'), + Tab(icon: Icon(Icons.event), text: 'Événements'), + Tab(icon: Icon(Icons.location_city), text: 'Établissements'), + Tab(icon: Icon(Icons.people), text: 'Social'), + Tab(icon: Icon(Icons.person), text: 'Profil'), + ], + ), ), - body: const Center( - child: Text('Bienvenue sur AfterWork!'), + body: TabBarView( + controller: _tabController, + children: [ + _getSelectedScreen(0), + _getSelectedScreen(1), + _getSelectedScreen(2), + _getSelectedScreen(3), + _getSelectedScreen(4), + ], ), + backgroundColor: Colors.black, // Arrière-plan de l'écran en noir ); } + + // Cette méthode retourne le widget correspondant à l'index sélectionné + Widget _getSelectedScreen(int index) { + switch (index) { + case 0: + return const Center( + child: Text( + 'Accueil', + style: TextStyle(color: Colors.white, fontSize: 24), + ), + ); + case 1: + return const Center( + child: Text( + 'Événements', + style: TextStyle(color: Colors.white, fontSize: 24), + ), + ); + case 2: + return const Center( + child: Text( + 'Établissements', + style: TextStyle(color: Colors.white, fontSize: 24), + ), + ); + case 3: + return const Center( + child: Text( + 'Social', + style: TextStyle(color: Colors.white, fontSize: 24), + ), + ); + case 4: + return const Center( + child: Text( + 'Profil', + style: TextStyle(color: Colors.white, fontSize: 24), + ), + ); + default: + return const Center( + child: Text( + 'Accueil', + style: TextStyle(color: Colors.white, fontSize: 24), + ), + ); + } + } } diff --git a/lib/presentation/screens/login/login_screen.dart b/lib/presentation/screens/login/login_screen.dart index 180f116..984daa8 100644 --- a/lib/presentation/screens/login/login_screen.dart +++ b/lib/presentation/screens/login/login_screen.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:afterwork/data/datasources/user_remote_data_source.dart'; import 'package:afterwork/data/models/user_model.dart'; +import 'package:afterwork/presentation/screens/home/home_screen.dart'; import 'package:http/http.dart' as http; class LoginScreen extends StatefulWidget { @@ -10,14 +11,36 @@ class LoginScreen extends StatefulWidget { _LoginScreenState createState() => _LoginScreenState(); } -class _LoginScreenState extends State { +class _LoginScreenState extends State with SingleTickerProviderStateMixin { final _formKey = GlobalKey(); - String _email = ''; // Initialisation par défaut - String _password = ''; // Initialisation par défaut + String _email = ''; + String _password = ''; bool _isPasswordVisible = false; + bool _isSubmitting = false; final UserRemoteDataSource _userRemoteDataSource = UserRemoteDataSource(http.Client()); + late AnimationController _controller; + late Animation _buttonScaleAnimation; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 300), + ); + _buttonScaleAnimation = Tween(begin: 1.0, end: 1.1).animate( + CurvedAnimation(parent: _controller, curve: Curves.easeInOut), + ); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + void _togglePasswordVisibility() { setState(() { _isPasswordVisible = !_isPasswordVisible; @@ -26,30 +49,30 @@ class _LoginScreenState extends State { void _submit() async { if (_formKey.currentState!.validate()) { + setState(() { + _isSubmitting = true; + }); _formKey.currentState!.save(); - print('Email: $_email'); // Pour vérifier la valeur de l'email - print('Mot de passe: $_password'); // Pour vérifier la valeur du mot de passe - - if (_email.isEmpty || _password.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Veuillez remplir tous les champs')), - ); - return; - } - try { UserModel user = await _userRemoteDataSource.authenticateUser(_email, _password); print('Connexion réussie : ${user.email}'); - // Naviguer vers la page d'accueil ou autre + + // Navigation vers la page d'accueil + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (context) => const HomeScreen()), + ); } catch (e) { print('Erreur lors de la connexion: $e'); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(e.toString())), ); + } finally { + setState(() { + _isSubmitting = false; + }); } - } else { - print('Formulaire invalide'); } } @@ -58,14 +81,16 @@ class _LoginScreenState extends State { final size = MediaQuery.of(context).size; return Scaffold( - backgroundColor: Colors.white, body: Stack( children: [ - // Arrière-plan - Positioned.fill( - child: Image.asset( - 'lib/assets/images/background.webp', // Remplace par le chemin de ton image - fit: BoxFit.cover, + // Arrière-plan avec dégradé + Container( + decoration: const BoxDecoration( + gradient: LinearGradient( + colors: [Color(0xFF4A90E2), Color(0xFF9013FE)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), ), ), // Contenu de la page @@ -77,27 +102,56 @@ class _LoginScreenState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Image.asset( - 'lib/assets/images/logo.png', - height: size.height * 0.2, + // Logo avec légère animation + AnimatedBuilder( + animation: _controller, + builder: (context, child) { + return Transform.scale( + scale: _buttonScaleAnimation.value, + child: child, + ); + }, + child: GestureDetector( + onTapDown: (_) => _controller.forward(), + onTapUp: (_) => _controller.reverse(), + child: Image.asset( + 'lib/assets/images/logo.png', + height: size.height * 0.2, + ), + ), ), const SizedBox(height: 20), const Text( 'Bienvenue sur AfterWork', style: TextStyle( - fontSize: 24, + fontSize: 26, fontWeight: FontWeight.bold, - color: Colors.blueAccent, + color: Colors.white, + shadows: [ + Shadow( + offset: Offset(0, 2), + blurRadius: 6, + color: Colors.black26, + ), + ], ), ), const SizedBox(height: 40), + // Champ Email TextFormField( - decoration: const InputDecoration( + decoration: InputDecoration( labelText: 'Email', - border: OutlineInputBorder(), - prefixIcon: Icon(Icons.email), + filled: true, + fillColor: Colors.white.withOpacity(0.1), + labelStyle: const TextStyle(color: Colors.white), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10.0), + borderSide: BorderSide.none, + ), + prefixIcon: const Icon(Icons.email, color: Colors.white), ), keyboardType: TextInputType.emailAddress, + style: const TextStyle(color: Colors.white), validator: (value) { if (value == null || value.isEmpty) { return 'Veuillez entrer votre email'; @@ -112,19 +166,28 @@ class _LoginScreenState extends State { }, ), const SizedBox(height: 20), + // Champ Mot de passe TextFormField( decoration: InputDecoration( labelText: 'Mot de passe', - border: const OutlineInputBorder(), - prefixIcon: const Icon(Icons.lock), + filled: true, + fillColor: Colors.white.withOpacity(0.1), + labelStyle: const TextStyle(color: Colors.white), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10.0), + borderSide: BorderSide.none, + ), + prefixIcon: const Icon(Icons.lock, color: Colors.white), suffixIcon: IconButton( icon: Icon( _isPasswordVisible ? Icons.visibility : Icons.visibility_off, + color: Colors.white, ), onPressed: _togglePasswordVisibility, ), ), obscureText: !_isPasswordVisible, + style: const TextStyle(color: Colors.white), validator: (value) { if (value == null || value.isEmpty) { return 'Veuillez entrer votre mot de passe'; @@ -139,29 +202,33 @@ class _LoginScreenState extends State { }, ), const SizedBox(height: 20), + // Bouton de connexion avec animation de soumission SizedBox( width: double.infinity, child: ElevatedButton( style: ElevatedButton.styleFrom( padding: const EdgeInsets.all(16.0), textStyle: const TextStyle(fontSize: 18), - backgroundColor: Colors.blueAccent, + backgroundColor: _isSubmitting ? Colors.grey : Colors.blueAccent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.0), ), ), - onPressed: _submit, - child: const Text('Connexion'), + onPressed: _isSubmitting ? null : _submit, + child: _isSubmitting + ? const CircularProgressIndicator(color: Colors.white) + : const Text('Connexion'), ), ), const SizedBox(height: 20), + // Lien pour s'inscrire TextButton( onPressed: () { // Naviguer vers la page d'inscription }, child: const Text( 'Pas encore de compte ? Inscrivez-vous', - style: TextStyle(color: Colors.blueAccent), + style: TextStyle(color: Colors.white), ), ), ], diff --git a/lib/presentation/screens/story/story_screen.dart b/lib/presentation/screens/story/story_screen.dart new file mode 100644 index 0000000..a8509de --- /dev/null +++ b/lib/presentation/screens/story/story_screen.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; + +class StoryScreen extends StatelessWidget { + const StoryScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Créer une Story'), + backgroundColor: Colors.blueAccent, + ), + body: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Texte de la Story', + style: TextStyle(fontSize: 18, color: Colors.white), + ), + const SizedBox(height: 8), + TextField( + maxLines: 5, + decoration: InputDecoration( + hintText: 'Rédigez votre Story', + filled: true, + fillColor: Colors.white.withOpacity(0.1), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10.0), + borderSide: BorderSide.none, + ), + hintStyle: const TextStyle(color: Colors.white70), + ), + style: const TextStyle(color: Colors.white), + ), + const SizedBox(height: 20), + ElevatedButton( + onPressed: () { + // Logique pour créer la story + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blueAccent, + padding: const EdgeInsets.all(16.0), + ), + child: const Text('Publier la Story'), + ), + ], + ), + ), + backgroundColor: Colors.black, + ); + } +} diff --git a/lib/presentation/state_management/event_bloc.dart b/lib/presentation/state_management/event_bloc.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/presentation/state_management/payment_bloc.dart b/lib/presentation/state_management/payment_bloc.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/presentation/state_management/reservation_bloc.dart b/lib/presentation/state_management/reservation_bloc.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/presentation/widgets/custom_appbar.dart b/lib/presentation/widgets/custom_appbar.dart new file mode 100644 index 0000000..635b287 --- /dev/null +++ b/lib/presentation/widgets/custom_appbar.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { + final String title; + final List actions; + + const CustomAppBar({ + Key? key, + required this.title, + this.actions = const [], + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return AppBar( + backgroundColor: Colors.black, + title: Text( + title, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + actions: actions, + ); + } + + @override + Size get preferredSize => const Size.fromHeight(56.0); +} diff --git a/lib/presentation/widgets/custom_button.dart b/lib/presentation/widgets/custom_button.dart index 485d6bd..08d499f 100644 --- a/lib/presentation/widgets/custom_button.dart +++ b/lib/presentation/widgets/custom_button.dart @@ -4,14 +4,25 @@ class CustomButton extends StatelessWidget { final String text; final VoidCallback onPressed; - const CustomButton({super.key, required this.text, required this.onPressed}); + const CustomButton({ + Key? key, + required this.text, + required this.onPressed, + }) : super(key: key); @override Widget build(BuildContext context) { return ElevatedButton( onPressed: onPressed, + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 16.0), + textStyle: const TextStyle(fontSize: 18), + backgroundColor: Colors.blueAccent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + ), + ), child: Text(text), ); } } - diff --git a/lib/presentation/widgets/custom_drawer.dart b/lib/presentation/widgets/custom_drawer.dart new file mode 100644 index 0000000..774bb63 --- /dev/null +++ b/lib/presentation/widgets/custom_drawer.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; + +class CustomDrawer extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Drawer( + child: ListView( + padding: EdgeInsets.zero, + children: [ + DrawerHeader( + decoration: BoxDecoration( + color: Colors.blueAccent, + ), + child: Text( + 'AfterWork', + style: TextStyle( + color: Colors.white, + fontSize: 24, + ), + ), + ), + ListTile( + leading: Icon(Icons.home), + title: Text('Accueil'), + onTap: () { + Navigator.pushNamed(context, '/home'); + }, + ), + ListTile( + leading: Icon(Icons.event), + title: Text('Événements'), + onTap: () { + Navigator.pushNamed(context, '/event'); + }, + ), + ListTile( + leading: Icon(Icons.camera_alt), // Icône mise à jour pour la story + title: Text('Story'), + onTap: () { + Navigator.pushNamed(context, '/story'); + }, + ), + ], + ), + ); + } +}