diff --git a/README.md b/README.md index c43db62..041de39 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,67 @@ -# web +# Travel Discovery App -A new Flutter project. +Une application Flutter moderne pour découvrir des destinations de voyage, inspirée d'un design élégant et moderne. -## Getting Started +## 🎨 Fonctionnalités -This project is a starting point for a Flutter application. +- **Interface moderne** : Design épuré avec des gradients et des ombres +- **Header personnalisé** : Profil utilisateur avec notifications +- **Section Hero** : Affichage spectaculaire du volcan Arenal avec effet de lave +- **Barre de recherche** : Interface intuitive pour rechercher des destinations +- **Thème personnalisé** : Couleurs et typographies cohérentes -A few resources to get you started if this is your first Flutter project: +## 🚀 Installation -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) +1. Assurez-vous d'avoir Flutter installé sur votre machine +2. Clonez ce repository +3. Naviguez vers le dossier du projet +4. Installez les dépendances : + ```bash + flutter pub get + ``` -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. +## 🏃‍♂️ Lancement + +Pour lancer l'application : + +```bash +flutter run +``` + +Pour lancer sur le web : +```bash +flutter run -d chrome +``` + +## 📱 Structure du projet + +``` +lib/ +├── main.dart # Point d'entrée de l'application +├── theme/ +│ └── app_theme.dart # Thème personnalisé avec couleurs et styles +└── widgets/ + ├── header_widget.dart # Widget du header avec profil utilisateur + ├── hero_section_widget.dart # Widget de la section hero avec le volcan + └── search_bar_widget.dart # Widget de la barre de recherche +``` + +## 🎨 Design + +L'application utilise : +- **Couleurs** : Palette orange/rouge pour évoquer la lave du volcan +- **Gradients** : Effets visuels pour simuler l'éruption volcanique +- **Typographie** : Police moderne et lisible +- **Ombres** : Effets de profondeur pour un design moderne + +## 🔧 Personnalisation + +Vous pouvez facilement personnaliser : +- Les couleurs dans `lib/theme/app_theme.dart` +- Les textes dans les widgets +- Les gradients pour créer d'autres effets visuels +- Les icônes et les images + +## 📄 Licence + +Ce projet est sous licence MIT. \ No newline at end of file diff --git a/assets/images/fond1.webp b/assets/images/fond1.webp new file mode 100644 index 0000000..e31c0cb Binary files /dev/null and b/assets/images/fond1.webp differ diff --git a/lib/main.dart b/lib/main.dart index 7b7f5b6..ff788f1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,122 +1,46 @@ import 'package:flutter/material.dart'; +import 'widgets/header_widget.dart'; +import 'widgets/hero_section_widget.dart'; +import 'widgets/search_bar_widget.dart'; +import 'theme/app_theme.dart'; void main() { - runApp(const MyApp()); + runApp(const TravelApp()); } -class MyApp extends StatelessWidget { - const MyApp({super.key}); +class TravelApp extends StatelessWidget { + const TravelApp({super.key}); - // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - // This is the theme of your application. - // - // TRY THIS: Try running your application with "flutter run". You'll see - // the application has a purple toolbar. Then, without quitting the app, - // try changing the seedColor in the colorScheme below to Colors.green - // and then invoke "hot reload" (save your changes or press the "hot - // reload" button in a Flutter-supported IDE, or press "r" if you used - // the command line to start the app). - // - // Notice that the counter didn't reset back to zero; the application - // state is not lost during the reload. To reset the state, use hot - // restart instead. - // - // This works for code too, not just values: Most code changes can be - // tested with just a hot reload. - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), - ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), + title: 'Travel Discovery', + theme: AppTheme.lightTheme, + home: const TravelHomePage() ); } } -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } +class TravelHomePage extends StatelessWidget { + const TravelHomePage({super.key}); @override Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. return Scaffold( - appBar: AppBar( - // TRY THIS: Try changing the color here to a specific color (to - // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar - // change color while the other colors stay the same. - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. + backgroundColor: AppTheme.backgroundColor, + body: SafeArea( child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - // - // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" - // action in the IDE, or press "p" in the console), to see the - // wireframe for each widget. - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('You have pushed the button this many times:'), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, + children: [ + SizedBox( + height: MediaQuery.of(context).size.height * 0.5, + child: const HeroSectionWidget(), ), + + const SizedBox(height: 20), ], ), ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. ); } -} + +} \ No newline at end of file diff --git a/lib/theme/app_theme.dart b/lib/theme/app_theme.dart new file mode 100644 index 0000000..0481604 --- /dev/null +++ b/lib/theme/app_theme.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart'; + +class AppTheme { + static const Color primaryColor = Color(0xFFFF6B35); + static const Color secondaryColor = Color(0xFF2C3E50); + static const Color backgroundColor = Color(0xFFF8F9FA); + static const Color cardColor = Colors.white; + static const Color textPrimary = Color(0xFF2C3E50); + static const Color textSecondary = Color(0xFF7F8C8D); + static const Color accentColor = Color(0xFFFFA726); + + static ThemeData get lightTheme { + return ThemeData( + useMaterial3: true, + colorScheme: ColorScheme.fromSeed( + seedColor: primaryColor, + brightness: Brightness.light, + ), + scaffoldBackgroundColor: backgroundColor, + appBarTheme: const AppBarTheme( + backgroundColor: Colors.transparent, + elevation: 0, + iconTheme: IconThemeData(color: textPrimary), + titleTextStyle: TextStyle( + color: textPrimary, + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + textTheme: const TextTheme( + headlineLarge: TextStyle( + fontSize: 32, + fontWeight: FontWeight.bold, + color: Colors.white, + height: 1.1, + ), + headlineMedium: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: textPrimary, + ), + titleLarge: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: textPrimary, + ), + titleMedium: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: textPrimary, + ), + bodyLarge: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: textPrimary, + ), + bodyMedium: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + color: textSecondary, + ), + bodySmall: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w400, + color: textSecondary, + ), + ), + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + backgroundColor: primaryColor, + foregroundColor: Colors.white, + elevation: 4, + shadowColor: primaryColor.withOpacity(0.3), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), + ), + ), + cardTheme: CardThemeData( + color: cardColor, + elevation: 8, + shadowColor: Colors.black.withOpacity(0.1), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + ), + ); + } + + // Gradients personnalisés + static const LinearGradient volcanoGradient = LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Color(0xFF1A1A1A), // Ciel sombre + Color(0xFF2D1B1B), // Rouge sombre + Color(0xFF8B0000), // Rouge foncé (lave) + Color(0xFFFF4500), // Rouge-orange (lave chaude) + Color(0xFFFF6347), // Orange (lave) + Color(0xFF4169E1), // Bleu ciel + Color(0xFF87CEEB), // Bleu clair + ], + stops: [0.0, 0.2, 0.4, 0.5, 0.6, 0.8, 1.0], + ); + + static const LinearGradient profileGradient = LinearGradient( + colors: [Color(0xFFFFB74D), Color(0xFFFF6B35)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ); + + static const LinearGradient cardGradient = LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [ + Color(0xFF1A1A2E), + Color(0xFF16213E), + Color(0xFF0F3460), + ], + ); +} diff --git a/lib/widgets/header_widget.dart b/lib/widgets/header_widget.dart new file mode 100644 index 0000000..ae572c3 --- /dev/null +++ b/lib/widgets/header_widget.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import '../theme/app_theme.dart'; + +class HeaderWidget extends StatelessWidget { + const HeaderWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), + child: Row( + children: [ + Container( + width: 50, + height: 50, + decoration: const BoxDecoration( + shape: BoxShape.circle, + gradient: AppTheme.profileGradient, + ), + child: const Icon( + Icons.person, + color: Colors.white, + size: 28, + ), + ), + + const SizedBox(width: 16), + + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Welcome Back', + style: Theme.of(context).textTheme.bodyMedium, + ), + Text( + 'John Anon', + style: Theme.of(context).textTheme.titleLarge, + ), + ], + ), + ), + Container( + width: 44, + height: 44, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(22), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: const Icon( + Icons.notifications_outlined, + color: Colors.black54, + size: 24, + ), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/hero_section_widget.dart b/lib/widgets/hero_section_widget.dart new file mode 100644 index 0000000..a98e0ff --- /dev/null +++ b/lib/widgets/hero_section_widget.dart @@ -0,0 +1,252 @@ +import 'package:flutter/material.dart'; +import '../theme/app_theme.dart'; + +class HeroSectionWidget extends StatelessWidget { + const HeroSectionWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + margin: const EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24), + gradient: AppTheme.cardGradient, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.3), + blurRadius: 20, + offset: const Offset(0, 10), + ), + ], + ), + child: Stack( + children: [ + Positioned.fill( + child: ClipRRect( + borderRadius: BorderRadius.circular(24), + child: Image.asset( + 'assets/images/fond1.webp', + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) { + return Container( + decoration: BoxDecoration( + gradient: AppTheme.volcanoGradient, + ), + ); + }, + ), + ), + ), + Positioned.fill( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24), + gradient: RadialGradient( + center: const Alignment(0.3, -0.4), + radius: 0.8, + colors: [ + Colors.transparent, + Colors.transparent, + const Color(0xFFFF4500).withOpacity(0.3), + const Color(0xFFFF6347).withOpacity(0.2), + Colors.transparent, + ], + stops: const [0.0, 0.3, 0.5, 0.7, 1.0], + ), + ), + ), + ), + Positioned.fill( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(24), + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.transparent, + Colors.black.withOpacity(0.7), + ], + ), + ), + ), + ), + Positioned( + top: 20, + left: 20, + right: 20, + child: Row( + children: [ + Container( + width: 50, + height: 50, + decoration: const BoxDecoration( + shape: BoxShape.circle, + gradient: AppTheme.profileGradient, + ), + child: const Icon( + Icons.person, + color: Colors.white, + size: 28, + ), + ), + + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Welcome Back', + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Colors.white.withOpacity(0.8), + ), + ), + Text( + 'John Anon', + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: Colors.white, + ), + ), + ], + ), + ), + Container( + width: 44, + height: 44, + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(22), + border: Border.all( + color: Colors.white.withOpacity(0.3), + width: 1, + ), + ), + child: const Icon( + Icons.notifications_outlined, + color: Colors.white, + size: 24, + ), + ), + ], + ), + ), + Positioned( + bottom: 80, + left: 24, + right: 24, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Icon( + Icons.location_on, + color: Colors.white, + size: 16, + ), + const SizedBox(width: 4), + const Text( + 'dans mon ventre', + style: TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + + const SizedBox(height: 8), + Text( + 'Notre poisson rouge', + style: Theme.of(context).textTheme.headlineLarge, + ), + + const SizedBox(height: 8), + Row( + children: [ + const Icon( + Icons.star, + color: AppTheme.accentColor, + size: 18, + ), + const SizedBox(width: 4), + const Text( + '4/5', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(width: 8), + Text( + '(500+ Review)', + style: TextStyle( + color: Colors.white.withOpacity(0.8), + fontSize: 14, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ], + ), + ), + Positioned( + bottom: 20, + left: 8, + right: 8, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 4), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.9), + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + ), + child: Row( + children: [ + Icon( + Icons.search, + color: AppTheme.textSecondary, + size: 24, + ), + const SizedBox(width: 12), + Expanded( + child: Text( + 'Search your destination...', + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + color: AppTheme.textSecondary, + ), + ), + ), + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(12), + ), + child: Icon( + Icons.tune, + color: AppTheme.textSecondary, + size: 20, + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/search_bar_widget.dart b/lib/widgets/search_bar_widget.dart new file mode 100644 index 0000000..75f8eca --- /dev/null +++ b/lib/widgets/search_bar_widget.dart @@ -0,0 +1,56 @@ +import 'package:flutter/material.dart'; +import '../theme/app_theme.dart'; + +class SearchBarWidget extends StatelessWidget { + const SearchBarWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 8), + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 4), + decoration: BoxDecoration( + color: AppTheme.cardColor, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 10, + offset: const Offset(0, 4), + ), + ], + ), + child: Row( + children: [ + Icon( + Icons.search, + color: AppTheme.textSecondary, + size: 24, + ), + const SizedBox(width: 12), + Expanded( + child: Text( + 'Search your destination...', + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + color: AppTheme.textSecondary, + ), + ), + ), + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(12), + ), + child: Icon( + Icons.tune, + color: AppTheme.textSecondary, + size: 20, + ), + ), + ], + ), + ); + } +}