diff --git a/assets/icons/active.png b/assets/icons/active.png new file mode 100644 index 0000000..e574dd1 Binary files /dev/null and b/assets/icons/active.png differ diff --git a/assets/icons/adventure.png b/assets/icons/adventure.png new file mode 100644 index 0000000..49141eb Binary files /dev/null and b/assets/icons/adventure.png differ diff --git a/assets/icons/balanced.png b/assets/icons/balanced.png new file mode 100644 index 0000000..77f2afc Binary files /dev/null and b/assets/icons/balanced.png differ diff --git a/assets/icons/calender.png b/assets/icons/calender.png new file mode 100644 index 0000000..6ef0c0b Binary files /dev/null and b/assets/icons/calender.png differ diff --git a/assets/icons/halal.png b/assets/icons/halal.png new file mode 100644 index 0000000..7933c99 Binary files /dev/null and b/assets/icons/halal.png differ diff --git a/assets/icons/hi_rate1.png b/assets/icons/hi_rate1.png new file mode 100644 index 0000000..6ddb0bb Binary files /dev/null and b/assets/icons/hi_rate1.png differ diff --git a/assets/icons/hi_rate2.png b/assets/icons/hi_rate2.png new file mode 100644 index 0000000..898a2c2 Binary files /dev/null and b/assets/icons/hi_rate2.png differ diff --git a/assets/icons/hi_rate3.png b/assets/icons/hi_rate3.png new file mode 100644 index 0000000..4b5d345 Binary files /dev/null and b/assets/icons/hi_rate3.png differ diff --git a/assets/icons/hi_rate4.png b/assets/icons/hi_rate4.png new file mode 100644 index 0000000..97020b2 Binary files /dev/null and b/assets/icons/hi_rate4.png differ diff --git a/assets/icons/kosher.png b/assets/icons/kosher.png new file mode 100644 index 0000000..812c36b Binary files /dev/null and b/assets/icons/kosher.png differ diff --git a/assets/icons/location.png b/assets/icons/location.png new file mode 100644 index 0000000..fa0a7c2 Binary files /dev/null and b/assets/icons/location.png differ diff --git a/assets/icons/magic_creation.png b/assets/icons/magic_creation.png new file mode 100644 index 0000000..248d465 Binary files /dev/null and b/assets/icons/magic_creation.png differ diff --git a/assets/icons/no_restrictions_food.png b/assets/icons/no_restrictions_food.png new file mode 100644 index 0000000..76372a1 Binary files /dev/null and b/assets/icons/no_restrictions_food.png differ diff --git a/assets/icons/pesc.png b/assets/icons/pesc.png new file mode 100644 index 0000000..0a601ec Binary files /dev/null and b/assets/icons/pesc.png differ diff --git a/assets/icons/relaxed.png b/assets/icons/relaxed.png new file mode 100644 index 0000000..1dea43c Binary files /dev/null and b/assets/icons/relaxed.png differ diff --git a/assets/icons/tr_rate1.png b/assets/icons/tr_rate1.png new file mode 100644 index 0000000..bf9e23a Binary files /dev/null and b/assets/icons/tr_rate1.png differ diff --git a/assets/icons/tr_rate2.png b/assets/icons/tr_rate2.png new file mode 100644 index 0000000..328a6cc Binary files /dev/null and b/assets/icons/tr_rate2.png differ diff --git a/assets/icons/tr_rate3.png b/assets/icons/tr_rate3.png new file mode 100644 index 0000000..f4bddcb Binary files /dev/null and b/assets/icons/tr_rate3.png differ diff --git a/assets/icons/tr_rate4.png b/assets/icons/tr_rate4.png new file mode 100644 index 0000000..525d302 Binary files /dev/null and b/assets/icons/tr_rate4.png differ diff --git a/assets/icons/veg.png b/assets/icons/veg.png new file mode 100644 index 0000000..d4ed7ba Binary files /dev/null and b/assets/icons/veg.png differ diff --git a/assets/icons/vegan.png b/assets/icons/vegan.png new file mode 100644 index 0000000..b7f5ac4 Binary files /dev/null and b/assets/icons/vegan.png differ diff --git a/assets/icons/wi_rate1.png b/assets/icons/wi_rate1.png new file mode 100644 index 0000000..b350337 Binary files /dev/null and b/assets/icons/wi_rate1.png differ diff --git a/assets/icons/wi_rate2.png b/assets/icons/wi_rate2.png new file mode 100644 index 0000000..f820239 Binary files /dev/null and b/assets/icons/wi_rate2.png differ diff --git a/assets/icons/wi_rate3.png b/assets/icons/wi_rate3.png new file mode 100644 index 0000000..1887a32 Binary files /dev/null and b/assets/icons/wi_rate3.png differ diff --git a/assets/icons/wi_rate4.png b/assets/icons/wi_rate4.png new file mode 100644 index 0000000..ad0f957 Binary files /dev/null and b/assets/icons/wi_rate4.png differ diff --git a/lib/common_packages/custom_filled_button.dart b/lib/common_packages/custom_filled_button.dart new file mode 100644 index 0000000..6a6e706 --- /dev/null +++ b/lib/common_packages/custom_filled_button.dart @@ -0,0 +1,51 @@ +import 'package:citycards_customer/common_packages/custom_text.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class CustomFilledButton extends StatelessWidget { + final double? width; + final String label; + final bool? showArrow; + final GestureTapCallback onTap; + + CustomFilledButton({ + super.key, + this.width = 266, + required this.onTap, + required this.label, + this.showArrow = false, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Container( + height: 42.h, + width: width, + decoration: BoxDecoration( + color: Color(0xFFF95F62), + borderRadius: BorderRadius.circular(38.r), + ), + child: Center( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CustomText( + text: label, + color: Colors.white, + size: 16.sp , + weight: FontWeight.w500, + ), + + if(showArrow!) + SizedBox(width: 8,), + if(showArrow!) + Icon(Icons.arrow_forward_ios_rounded,size: 18.sp, color: Colors.white,) + ], + ), + ), + ), + ); + } +} diff --git a/lib/common_packages/custom_text.dart b/lib/common_packages/custom_text.dart index 9bdbdce..6602eea 100644 --- a/lib/common_packages/custom_text.dart +++ b/lib/common_packages/custom_text.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flutter_screenutil/flutter_screenutil.dart'; class CustomText extends StatelessWidget { final FontWeight? weight; diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index 25d5da2..6fc3de0 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -3,6 +3,9 @@ import 'package:citycards_customer/common_bloc/language_selection_bloc.dart'; import 'package:citycards_customer/contact_us/contact_us_view.dart'; import 'package:citycards_customer/edit_profile/edit_profile_view.dart'; import 'package:citycards_customer/faq/faq_view.dart'; +import 'package:citycards_customer/itinerary_creation/bloc/itinerary_steps_selection_bloc.dart'; +import 'package:citycards_customer/itinerary_creation/itinerary_creation_start_view.dart'; +import 'package:citycards_customer/itinerary_creation/itinerary_creation_view.dart'; import 'package:citycards_customer/privacy/privacy_view.dart'; import 'package:citycards_customer/terms_and_condition/terms_and_condition_view.dart'; import 'package:flutter/material.dart'; @@ -65,6 +68,21 @@ class AppRouter { }, ); + case RouteConstants.itineraryCreationStart: + return MaterialPageRoute( + builder: (_) { + return ItineraryCreationStartPage(); + }, + ); + + case RouteConstants.itineraryCreation: + return MaterialPageRoute( + builder: (_) { + return BlocProvider(create: (_) => ItineraryStepNavigationBloc(), + child: ItineraryCreationPage(), + ); + }, + ); default: return MaterialPageRoute( builder: (_) => diff --git a/lib/core/route_constants.dart b/lib/core/route_constants.dart index 32c0909..104d921 100644 --- a/lib/core/route_constants.dart +++ b/lib/core/route_constants.dart @@ -9,4 +9,9 @@ class RouteConstants { static const String termsAndCondition = '/termsAndCondition'; static const String privacyPolicy = '/privacyPolicy'; static const String faq = '/faq'; + + /****************************** ITINERARY CREATION ************************************/ + + static const String itineraryCreationStart = '/itineraryCreationStart'; + static const String itineraryCreation = '/itineraryCreation'; } diff --git a/lib/itinerary_creation/bloc/itinerary_steps_selection_bloc.dart b/lib/itinerary_creation/bloc/itinerary_steps_selection_bloc.dart new file mode 100644 index 0000000..1a89d7d --- /dev/null +++ b/lib/itinerary_creation/bloc/itinerary_steps_selection_bloc.dart @@ -0,0 +1,36 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; + +abstract class ItineraryStepNavigationEvent {} + +class ItineraryStepNavigationNextEvent extends ItineraryStepNavigationEvent {} + +class ItineraryStepNavigationPreviousEvent extends ItineraryStepNavigationEvent {} + +class ItineraryStepNavigationState { + final int selectedIndex; + const ItineraryStepNavigationState(this.selectedIndex); +} + +class ItineraryStepNavigationBloc + extends Bloc { + final int maxIndex; // maximum index allowed + + ItineraryStepNavigationBloc({this.maxIndex = 2}) + : super(const ItineraryStepNavigationState(0)) { + + + on((event, emit) { + final nextIndex = state.selectedIndex + 1; + if (nextIndex <= 10) { + emit(ItineraryStepNavigationState(nextIndex)); + } + }); + + on((event, emit) { + final prevIndex = state.selectedIndex - 1; + if (prevIndex >= 0) { + emit(ItineraryStepNavigationState(prevIndex)); + } + }); + } +} diff --git a/lib/itinerary_creation/itinerary_creation_start_view.dart b/lib/itinerary_creation/itinerary_creation_start_view.dart new file mode 100644 index 0000000..9f8905f --- /dev/null +++ b/lib/itinerary_creation/itinerary_creation_start_view.dart @@ -0,0 +1,90 @@ +import 'package:citycards_customer/common_packages/custom_filled_button.dart'; +import 'package:citycards_customer/common_packages/custom_text.dart'; +import 'package:citycards_customer/core/route_constants.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class ItineraryCreationStartPage extends StatelessWidget { + const ItineraryCreationStartPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Color(0xFFFFF5F5), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + height: 103.h, + width: 103.w, + + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(24.r), + boxShadow: [ + BoxShadow( + color: Colors.black12, + offset: Offset(0, 4), + blurRadius: 5, + ), + ], + ), + child: Center( + child: Image.asset("assets/icons/magic_creation.png", scale: 4), + ), + ), + + SizedBox(height: 34.h), + Text.rich( + TextSpan( + children: [ + TextSpan( + text: "Create your", + style: TextStyle( + color: Color(0xFF101828), + fontSize: 24.sp, + fontWeight: FontWeight.w500, + ), + ), + TextSpan( + text: " magic itinerary", + style: TextStyle( + color: Color(0xFFF95F62), + fontSize: 24.sp, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + + SizedBox(height: 13.h), + Padding( + padding: EdgeInsets.symmetric(horizontal: 25.w), + child: Text( + "Answer a few quick questions and we'll craft a personalized travel experience just for you ✨", + style: TextStyle(fontSize: 14, color: Color(0xFF4A5565)), + textAlign: TextAlign.center, + ), + ), + SizedBox(height: 47.h), + CustomFilledButton( + onTap: () { + Navigator.pushNamed(context, RouteConstants.itineraryCreation); + }, + showArrow: true, + label: "Let’s Get Started", + ), + SizedBox(height: 38.h), + CustomText( + text: "Takes only 2 minutes ⏱️", + color: Color(0xFF6A7282), + size: 14.sp, + ), + ], + ), + ), + ); + } +} diff --git a/lib/itinerary_creation/itinerary_creation_steps/art_gallery_selection_view.dart b/lib/itinerary_creation/itinerary_creation_steps/art_gallery_selection_view.dart new file mode 100644 index 0000000..45e9074 --- /dev/null +++ b/lib/itinerary_creation/itinerary_creation_steps/art_gallery_selection_view.dart @@ -0,0 +1,74 @@ +import 'package:citycards_customer/common_packages/custom_text.dart'; +import 'package:citycards_customer/itinerary_creation/bloc/itinerary_steps_selection_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class ArtGallerySelectionView extends StatelessWidget { + ArtGallerySelectionView({super.key}); + + final List> options = [ + {"icon": "😴", "name": "Not interested"}, + {"icon": "🤔", "name": "Maybe one or two"}, + {"icon": "😊", "name": "Yes, sounds good!"}, + {"icon": "🤩", "name": "Absolutely love them!"}, + ]; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "👋 Hello! We'd love to know more about you. Do you enjoy visiting museums and art galleries?", + style: TextStyle( + color: Color(0xFF101828), + fontSize: 24.sp, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 32.h), + ...List.generate(options.length, (index) { + final item = options[index]; + return Padding( + padding: EdgeInsets.only(bottom: 12.h), + child: GestureDetector( + onTap: () { + context.read().add( + ItineraryStepNavigationNextEvent(), + ); + }, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 24.w), + height: 83.h, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(28.r), + ), + alignment: Alignment.center, + child: Row( + children: [ + CustomText( + text: item['icon'] ?? "", + size: 36.sp, + color: const Color(0xFF101828), + weight: FontWeight.w500, + ), + SizedBox(width: 16.w,), + CustomText( + text: item['name'] ?? "", + size: 16.sp, + color: const Color(0xFF101828), + weight: FontWeight.w500, + ), + ], + ), + ), + ), + ); + }), + ], + ); + } +} diff --git a/lib/itinerary_creation/itinerary_creation_steps/city_selection_view.dart b/lib/itinerary_creation/itinerary_creation_steps/city_selection_view.dart new file mode 100644 index 0000000..8ac774d --- /dev/null +++ b/lib/itinerary_creation/itinerary_creation_steps/city_selection_view.dart @@ -0,0 +1,116 @@ +import 'package:citycards_customer/common_packages/custom_filled_button.dart'; +import 'package:citycards_customer/common_packages/custom_text.dart'; +import 'package:citycards_customer/itinerary_creation/bloc/itinerary_steps_selection_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class CitySelectionView extends StatelessWidget { + CitySelectionView({super.key}); + + final List> cityList = [ + {"flag": "🇫🇷", "city": "Paris"}, + {"flag": "🇯🇵", "city": "Tokyo"}, + {"flag": "🇺🇸", "city": "New York"}, + {"flag": "🇬🇧", "city": "London"}, + {"flag": "🇪🇸", "city": "Barcelona"}, + {"flag": "🇦🇪", "city": "Dubai"}, + {"flag": "🇮🇹", "city": "Rome"}, + {"flag": "🇹🇭", "city": "Bangkok"}, + ]; + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "👋 Hello! We'd love to know more about you. Which city are you visiting?", + style: TextStyle( + color: Color(0xFF101828), + fontSize: 24.sp, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 32.h), + Container( + height: 56.h, + width: double.infinity, + padding: EdgeInsets.only(left: 20.w), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Color(0xFFF95F62)), + borderRadius: BorderRadius.circular(28.r), + ), + child: Row( + children: [ + Image.asset("assets/icons/location.png", scale: 4), + SizedBox(width: 12.w), + CustomText( + text: "Tokyo", + color: Color(0xFF737373), + size: 14.sp, + ), + ], + ), + ), + SizedBox(height: 16.h), + Align( + alignment: Alignment.topLeft, + child: CustomText( + text: "Popular destinations", + size: 16.sp, + weight: FontWeight.w500, + color: Color(0xFF6A7282), + ), + ), + SizedBox(height: 10.h), + SizedBox( + height: 175.h, + child: GridView.builder( + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, + mainAxisSpacing: 16.h, + crossAxisSpacing: 16.w, + ), + itemCount: cityList.length, + itemBuilder: (context, index) { + final item = cityList[index]; + return Container( + height: 78.h, + width: 76.w, + + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CustomText(text: item['flag'] ?? ""), + SizedBox(height: 4.h), + CustomText( + text: item['city'] ?? "", + size: 12.sp, + color: Color(0xFF364153), + ), + ], + ), + ); + }, + ), + ), + SizedBox(height: 40.h), + CustomFilledButton(onTap: () { + context.read().add( + ItineraryStepNavigationNextEvent(), + ); + }, label: "Continue", showArrow: true), + ], + ), + ); + } +} diff --git a/lib/itinerary_creation/itinerary_creation_steps/date_selection_view.dart b/lib/itinerary_creation/itinerary_creation_steps/date_selection_view.dart new file mode 100644 index 0000000..2a65ca7 --- /dev/null +++ b/lib/itinerary_creation/itinerary_creation_steps/date_selection_view.dart @@ -0,0 +1,65 @@ +import 'package:citycards_customer/common_packages/custom_filled_button.dart'; +import 'package:citycards_customer/common_packages/custom_text.dart'; +import 'package:citycards_customer/itinerary_creation/bloc/itinerary_steps_selection_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class DateSelectionView extends StatelessWidget { + const DateSelectionView({super.key}); + + @override + Widget build(BuildContext context) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "👋 Hello! We'd love to know more about you. When are you visiting?", + style: TextStyle( + color: Color(0xFF101828), + fontSize: 24.sp, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 32.h), + + Container( + height: 90.h, + padding: EdgeInsets.symmetric(horizontal: 20.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(28), + border: Border.all(color: Color(0xFFF95F62), width: 1.1.w), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Image.asset("assets/icons/calender.png", scale: 4), + + CustomText( + text: "Wednesday, October 15, 2025", + size: 14.sp, + color: Color(0xFF101828), + ), + + Icon(Icons.check_circle, color: Color(0xFFF95F62)), + ], + ), + ), + SizedBox(height: 32.h), + CustomFilledButton( + onTap: () { + context.read().add( + ItineraryStepNavigationNextEvent(), + ); + }, + label: "Continue", + showArrow: true, + ), + ], + ), + ); + } +} diff --git a/lib/itinerary_creation/itinerary_creation_steps/dietary_selection_view.dart b/lib/itinerary_creation/itinerary_creation_steps/dietary_selection_view.dart new file mode 100644 index 0000000..9e8b205 --- /dev/null +++ b/lib/itinerary_creation/itinerary_creation_steps/dietary_selection_view.dart @@ -0,0 +1,107 @@ + +import 'package:citycards_customer/common_packages/custom_filled_button.dart'; +import 'package:citycards_customer/common_packages/custom_text.dart'; +import 'package:citycards_customer/itinerary_creation/bloc/itinerary_steps_selection_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class DietarySelectionView extends StatefulWidget { + const DietarySelectionView({super.key}); + + @override + State createState() => _DietarySelectionViewState(); +} + +class _DietarySelectionViewState extends State { + int selectedIndex = -1; + + final List> options = [ + { + "icon": "assets/icons/no_restrictions_food.png", + "name": "No Restrictions", + }, + {"icon": "assets/icons/veg.png", "name": "Vegetarian"}, + {"icon": "assets/icons/vegan.png", "name": "Vegan"}, + {"icon": "assets/icons/pesc.png", "name": "Pescatarian"}, + {"icon": "assets/icons/halal.png", "name": "Halal"}, + {"icon": "assets/icons/kosher.png", "name": "Kosher"}, + ]; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "👋 Hello! We'd love to know more about you. Do you follow any dietary preferences?", + style: TextStyle( + color: Color(0xFF101828), + fontSize: 24.sp, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 12.h), + CustomText( + text: "Select all that apply", + size: 14.sp, + color: const Color(0xFF6A7282), + ), + SizedBox(height: 38.h), + SizedBox( + height: 350.h, + child: GridView.builder( + physics: NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + mainAxisSpacing: 12, + crossAxisSpacing: 16, + crossAxisCount: 2, + childAspectRatio: 1.7, + ), + itemCount: options.length, + itemBuilder: (BuildContext context, int index) { + final item = options[index]; + return GestureDetector( + onTap: () {}, + child: Container( + width: 168.w, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(24), + ), + alignment: Alignment.center, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset(item["icon"] ?? "", scale: 4), + SizedBox(height: 8), + CustomText( + text: item["name"] ?? "", + size: 16.sp, + weight: FontWeight.w500, + color: const Color(0xFF364153), + ), + ], + ), + ), + ); + }, + ), + ), + + SizedBox(height: 41.h), + CustomFilledButton( + onTap: () { + context.read().add( + ItineraryStepNavigationNextEvent(), + ); + }, + label: "Continue", + showArrow: true, + ), + ], + ); + } +} diff --git a/lib/itinerary_creation/itinerary_creation_steps/energy_selection_view.dart b/lib/itinerary_creation/itinerary_creation_steps/energy_selection_view.dart new file mode 100644 index 0000000..c5fd1e7 --- /dev/null +++ b/lib/itinerary_creation/itinerary_creation_steps/energy_selection_view.dart @@ -0,0 +1,72 @@ +import 'package:citycards_customer/common_packages/custom_filled_button.dart'; +import 'package:citycards_customer/common_packages/custom_text.dart'; +import 'package:citycards_customer/itinerary_creation/bloc/itinerary_steps_selection_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class EnergySelectionView extends StatelessWidget { + EnergySelectionView({super.key}); + + final List> options = [ + {"img": "assets/icons/relaxed.png", "name": "Relaxed & Chill"}, + {"img": "assets/icons/balanced.png", "name": "Balanced Mix"}, + {"img": "assets/icons/active.png", "name": "Active & Energetic"}, + {"img": "assets/icons/adventure.png", "name": "Full Adventure"}, + ]; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "👋 Hello! We'd love to know more about you. What kind of energy are you after on this trip?", + style: TextStyle( + color: Color(0xFF101828), + fontSize: 24.sp, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 32.h), + ...List.generate(options.length, (index) { + + final item = options[index]; + return Padding( + padding: EdgeInsets.only(bottom: 12.h), + child: GestureDetector( + onTap: (){ + context.read().add( + ItineraryStepNavigationNextEvent(), + ); + }, + child: Container( + height: 86.h, + padding: EdgeInsets.symmetric(horizontal: 24.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(28.r), + ), + alignment: Alignment.center, + child: Row( + children: [ + Image.asset(item['img'] ?? "",scale: 4,), + SizedBox(width: 15,), + CustomText( + text: item['name'] ?? "", + size: 14.sp, + color: const Color(0xFF101828), + weight: FontWeight.w500, + ), + ], + ), + ), + ), + ); + }), + + ], + ); + } +} diff --git a/lib/itinerary_creation/itinerary_creation_steps/historical_site_rating_view.dart b/lib/itinerary_creation/itinerary_creation_steps/historical_site_rating_view.dart new file mode 100644 index 0000000..170f787 --- /dev/null +++ b/lib/itinerary_creation/itinerary_creation_steps/historical_site_rating_view.dart @@ -0,0 +1,82 @@ +import 'package:citycards_customer/common_packages/custom_text.dart'; +import 'package:citycards_customer/itinerary_creation/bloc/itinerary_steps_selection_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class HistoricalSiteRatingView extends StatelessWidget { + HistoricalSiteRatingView({super.key}); + + final List> historicalRatingOption = [ + {"icon": "assets/icons/hi_rate1.png", "option": "Not interested"}, + {"icon": "assets/icons/hi_rate2.png", "option": "A few key sites"}, + {"icon": "assets/icons/hi_rate3.png", "option": "Yes, I enjoy them!"}, + {"icon": "assets/icons/hi_rate4.png", "option": "Can't miss them!"}, + ]; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CustomText(text: "🏛️", size: 36.sp), + SizedBox(height: 9.h), + Text( + "Do you enjoy cultural landmarks and historical sites?", + style: TextStyle( + color: Color(0xFF101828), + fontSize: 24.sp, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 30.h), + ...List.generate(historicalRatingOption.length, (index) { + final item = historicalRatingOption[index]; + return Padding( + padding: EdgeInsets.only(bottom: 12.h), + child: GestureDetector( + onTap: () { + context.read().add( + ItineraryStepNavigationNextEvent(), + ); + }, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 24.w), + height: 82.h, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16.r), + border: Border.all( + color: Color(0xFFE5E7EB), + width: 1.1 + ), + boxShadow: [ + BoxShadow( + color: Colors.black12, + offset: Offset(1,2), + blurRadius: 1, + ) + ] + ), + alignment: Alignment.center, + child: Row( + children: [ + Image.asset(item['icon']?? "", scale: 4,), + SizedBox(width: 16.w,), + CustomText( + text: item['option'] ?? "", + size: 16.sp, + color: const Color(0xFF101828), + weight: FontWeight.w500, + ), + ], + ), + ), + ), + ); + }), + ], + ); + } +} diff --git a/lib/itinerary_creation/itinerary_creation_steps/itinerary_completion_view.dart b/lib/itinerary_creation/itinerary_creation_steps/itinerary_completion_view.dart new file mode 100644 index 0000000..5c32b82 --- /dev/null +++ b/lib/itinerary_creation/itinerary_creation_steps/itinerary_completion_view.dart @@ -0,0 +1,135 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:citycards_customer/common_packages/custom_text.dart'; +import 'package:citycards_customer/common_packages/custom_filled_button.dart'; + +class ItineraryCompletionView extends StatelessWidget { + const ItineraryCompletionView({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color(0xFFFFF5F5), + body: SingleChildScrollView( + child: Column( + children: [ + Column( + children: [ + SizedBox(height: 26.h), + CustomText(text: "🎉", size: 60.sp), + SizedBox(height: 32.h), + Text( + "All set! Your travel profile is complete", + textAlign: TextAlign.center, + style: TextStyle( + color: const Color(0xFF101828), + fontSize: 24.sp, + fontWeight: FontWeight.w500, + ), + ), + SizedBox(height: 4.h), + Text( + "We’ve got everything we need to plan your perfect trip", + style: TextStyle(color: Color(0xFF6A7282), fontSize: 14), + textAlign: TextAlign.center, + ), + SizedBox(height: 32.h), + Container( + width: double.infinity, + padding: EdgeInsets.symmetric( + horizontal: 16.w, + vertical: 20.h, + ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(24.r), + border: Border.all(color: Color(0xFFF3F4F6), width: 1.1), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomText( + text: "Your Profile:", + size: 16.sp, + weight: FontWeight.w500, + color: const Color(0xFF364153), + ), + SizedBox(height: 16.h), + _buildProfileRow("Visit Date", "Oct 15, 2025"), + _buildProfileRow("City", "Tokyo"), + _buildProfileRow("Energy", "Relaxed"), + _buildProfileRow("With kids", "Yes"), + _buildProfileRow("Dietary", "Vegan"), + _buildProfileRow("Museums", "⭐"), + _buildProfileRow("Scenic", "⭐"), + _buildProfileRow("Cultural", "⭐⭐⭐"), + _buildProfileRow("Wildlife", "⭐⭐"), + _buildProfileRow("Shopping", "⭐⭐⭐"), + ], + ), + ), + SizedBox(height: 32.h), + OutlinedButton( + style: OutlinedButton.styleFrom( + side: const BorderSide( + color: Color(0xFFE5E7EB), + width: 1.1, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(40), + ), + minimumSize: Size(double.infinity, 42.h), + ), + onPressed: () { + // Reset or restart flow + }, + child: CustomText( + text: "Start Over", + size: 16.sp, + color: const Color(0xFF364153), + ), + ), + SizedBox(height: 12.h), + CustomFilledButton( + width: double.infinity, + label: "Get My Trip Plan", + showArrow: true, + onTap: () { + // Navigate to next step + }, + ), + ], + ), + SizedBox(height: 32.h), + + // Profile summary card + ], + ), + ), + ); + } + + Widget _buildProfileRow(String title, String value) { + return Container( + height: 44.h, + margin: EdgeInsets.only(bottom: 8.h), + padding: EdgeInsets.symmetric(horizontal: 12.w), + decoration: BoxDecoration( + color: const Color(0xFFF9FAFB), + borderRadius: BorderRadius.circular(16.r), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomText(text: title, size: 14.sp, color: const Color(0xFF4A5565)), + CustomText( + text: value, + size: 14.sp, + weight: FontWeight.w400, + color: const Color(0xFF101828), + ), + ], + ), + ); + } +} diff --git a/lib/itinerary_creation/itinerary_creation_steps/kids_selection_view.dart b/lib/itinerary_creation/itinerary_creation_steps/kids_selection_view.dart new file mode 100644 index 0000000..4189476 --- /dev/null +++ b/lib/itinerary_creation/itinerary_creation_steps/kids_selection_view.dart @@ -0,0 +1,73 @@ +import 'package:citycards_customer/common_packages/custom_filled_button.dart'; +import 'package:citycards_customer/common_packages/custom_text.dart'; +import 'package:citycards_customer/itinerary_creation/bloc/itinerary_steps_selection_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class KidsSelectionView extends StatelessWidget { + KidsSelectionView({super.key}); + + final List> options = [ + {"icon": "🎈", "option": "Yes!"}, + {"icon": "🎒", "option": "No"}, + ]; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "👋 Hello! We'd love to know more about you. Are you travelling with kids?", + style: TextStyle( + color: Color(0xFF101828), + fontSize: 24.sp, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 40.h), + ...List.generate(options.length, (index) { + final item = options[index]; + return Padding( + padding: EdgeInsets.only(bottom: 12.h), + child: GestureDetector( + onTap: () { + context.read().add( + ItineraryStepNavigationNextEvent(), + ); + }, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 24.w), + height: 82.h, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(28.r), + ), + alignment: Alignment.center, + child: Row( + children: [ + CustomText( + text: item["icon"] ?? "", + size: 36.sp, + color: const Color(0xFF101828), + weight: FontWeight.w500, + ), + SizedBox(width: 16.w), + CustomText( + text: item["option"] ?? "", + size: 14.sp, + color: const Color(0xFF101828), + weight: FontWeight.w500, + ), + ], + ), + ), + ), + ); + }), + ], + ); + } +} diff --git a/lib/itinerary_creation/itinerary_creation_steps/scenic_viewpoints_rating_view.dart b/lib/itinerary_creation/itinerary_creation_steps/scenic_viewpoints_rating_view.dart new file mode 100644 index 0000000..4da81c7 --- /dev/null +++ b/lib/itinerary_creation/itinerary_creation_steps/scenic_viewpoints_rating_view.dart @@ -0,0 +1,74 @@ +import 'package:citycards_customer/common_packages/custom_text.dart'; +import 'package:citycards_customer/itinerary_creation/bloc/itinerary_steps_selection_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class ScenicViewpointsRatingView extends StatelessWidget { + ScenicViewpointsRatingView({super.key}); + + final List> options = [ + {"icon": "😐", "name": "Not my thing"}, + {"icon": "📸", "name": "If we have time"}, + {"icon": "😍", "name": "Yes, definitely!"}, + {"icon": "🌄", "name": "Must-have!"}, + ]; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "👋 Hello! We'd love to know more about you. Do you enjoy visiting museums and art galleries?", + style: TextStyle( + color: Color(0xFF101828), + fontSize: 24.sp, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 32.h), + ...List.generate(options.length, (index) { + final item = options[index]; + return Padding( + padding: EdgeInsets.only(bottom: 12.h), + child: GestureDetector( + onTap: () { + context.read().add( + ItineraryStepNavigationNextEvent(), + ); + }, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 24.w), + height: 83.h, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(28.r), + ), + alignment: Alignment.center, + child: Row( + children: [ + CustomText( + text: item['icon'] ?? "", + size: 36.sp, + color: const Color(0xFF101828), + weight: FontWeight.w500, + ), + SizedBox(width: 16.w,), + CustomText( + text: item['name'] ?? "", + size: 16.sp, + color: const Color(0xFF101828), + weight: FontWeight.w500, + ), + ], + ), + ), + ), + ); + }), + ], + ); + } +} diff --git a/lib/itinerary_creation/itinerary_creation_steps/shopping_rating_view.dart b/lib/itinerary_creation/itinerary_creation_steps/shopping_rating_view.dart new file mode 100644 index 0000000..e1921b9 --- /dev/null +++ b/lib/itinerary_creation/itinerary_creation_steps/shopping_rating_view.dart @@ -0,0 +1,83 @@ +import 'package:citycards_customer/common_packages/custom_text.dart'; +import 'package:citycards_customer/itinerary_creation/bloc/itinerary_steps_selection_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class ShoppingRatingView extends StatelessWidget { + ShoppingRatingView({super.key}); + + final List> shoppingRatingOption = [ + {"icon": "assets/icons/tr_rate1.png", "option": "Not interested"}, + {"icon": "assets/icons/tr_rate2.png", "option": "Just for souvenirs"}, + {"icon": "assets/icons/tr_rate3.png", "option": "Love browsing!"}, + {"icon": "assets/icons/tr_rate4.png", "option": "Shopping is a must!"}, + ]; + + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CustomText(text: "🛒", size: 36.sp), + SizedBox(height: 9.h), + Text( + "How do you feel about shopping during your trip?", + style: TextStyle( + color: Color(0xFF101828), + fontSize: 24.sp, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 30.h), + ...List.generate(shoppingRatingOption.length, (index) { + final item = shoppingRatingOption[index]; + return Padding( + padding: EdgeInsets.only(bottom: 12.h), + child: GestureDetector( + onTap: () { + context.read().add( + ItineraryStepNavigationNextEvent(), + ); + }, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 24.w), + height: 82.h, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16.r), + border: Border.all( + color: Color(0xFFE5E7EB), + width: 1.1 + ), + boxShadow: [ + BoxShadow( + color: Colors.black12, + offset: Offset(1,2), + blurRadius: 1, + ) + ] + ), + alignment: Alignment.center, + child: Row( + children: [ + Image.asset(item['icon']?? "", scale: 4,), + SizedBox(width: 16.w,), + CustomText( + text: item['option'] ?? "", + size: 16.sp, + color: const Color(0xFF101828), + weight: FontWeight.w500, + ), + ], + ), + ), + ), + ); + }), + ], + ); + } +} diff --git a/lib/itinerary_creation/itinerary_creation_steps/wildlife_rating_view.dart b/lib/itinerary_creation/itinerary_creation_steps/wildlife_rating_view.dart new file mode 100644 index 0000000..1f886bf --- /dev/null +++ b/lib/itinerary_creation/itinerary_creation_steps/wildlife_rating_view.dart @@ -0,0 +1,83 @@ +import 'package:citycards_customer/common_packages/custom_text.dart'; +import 'package:citycards_customer/itinerary_creation/bloc/itinerary_steps_selection_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class WildlifeRatingView extends StatelessWidget { + WildlifeRatingView({super.key}); + + + final List> wildlifeRatingOption = [ + {"icon": "assets/icons/wi_rate1.png", "option": "No thanks"}, + {"icon": "assets/icons/wi_rate2.png", "option": "If it happens naturally"}, + {"icon": "assets/icons/wi_rate3.png", "option": "Yes, would be nice!"}, + {"icon": "assets/icons/wi_rate4.png", "option": "Absolutely essential!"}, + ]; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CustomText(text: "🦒", size: 36.sp), + SizedBox(height: 9.h), + Text( + "Would you like to include wildlife experiences?", + style: TextStyle( + color: Color(0xFF101828), + fontSize: 24.sp, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 30.h), + ...List.generate(wildlifeRatingOption.length, (index) { + final item = wildlifeRatingOption[index]; + return Padding( + padding: EdgeInsets.only(bottom: 12.h), + child: GestureDetector( + onTap: () { + context.read().add( + ItineraryStepNavigationNextEvent(), + ); + }, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 24.w), + height: 82.h, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16.r), + border: Border.all( + color: Color(0xFFE5E7EB), + width: 1.1 + ), + boxShadow: [ + BoxShadow( + color: Colors.black12, + offset: Offset(1,2), + blurRadius: 1, + ) + ] + ), + alignment: Alignment.center, + child: Row( + children: [ + Image.asset(item['icon']?? "", scale: 4,), + SizedBox(width: 16.w,), + CustomText( + text: item['option'] ?? "", + size: 16.sp, + color: const Color(0xFF101828), + weight: FontWeight.w500, + ), + ], + ), + ), + ), + ); + }), + ], + ); + } +} diff --git a/lib/itinerary_creation/itinerary_creation_view.dart b/lib/itinerary_creation/itinerary_creation_view.dart new file mode 100644 index 0000000..5fa296d --- /dev/null +++ b/lib/itinerary_creation/itinerary_creation_view.dart @@ -0,0 +1,122 @@ +import 'package:citycards_customer/itinerary_creation/bloc/itinerary_steps_selection_bloc.dart'; +import 'package:citycards_customer/itinerary_creation/itinerary_creation_steps/art_gallery_selection_view.dart'; +import 'package:citycards_customer/itinerary_creation/itinerary_creation_steps/city_selection_view.dart'; +import 'package:citycards_customer/itinerary_creation/itinerary_creation_steps/date_selection_view.dart'; +import 'package:citycards_customer/itinerary_creation/itinerary_creation_steps/dietary_selection_view.dart'; +import 'package:citycards_customer/itinerary_creation/itinerary_creation_steps/energy_selection_view.dart'; +import 'package:citycards_customer/itinerary_creation/itinerary_creation_steps/historical_site_rating_view.dart'; +import 'package:citycards_customer/itinerary_creation/itinerary_creation_steps/itinerary_completion_view.dart'; +import 'package:citycards_customer/itinerary_creation/itinerary_creation_steps/kids_selection_view.dart'; +import 'package:citycards_customer/itinerary_creation/itinerary_creation_steps/scenic_viewpoints_rating_view.dart'; +import 'package:citycards_customer/itinerary_creation/itinerary_creation_steps/shopping_rating_view.dart'; +import 'package:citycards_customer/itinerary_creation/itinerary_creation_steps/wildlife_rating_view.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class ItineraryCreationPage extends StatefulWidget { + const ItineraryCreationPage({super.key}); + + @override + State createState() => _ItineraryCreationPageState(); +} + +class _ItineraryCreationPageState extends State { + final PageController _pageController = PageController(); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Color(0xFFFFF5F5), + appBar: AppBar( + backgroundColor: Color(0xFFFFF5F5), + centerTitle: true, + leading: GestureDetector( + onTap: () { + context.read().add( + ItineraryStepNavigationPreviousEvent(), + ); + }, + child: Icon(Icons.arrow_back), + ), + title: + BlocBuilder< + ItineraryStepNavigationBloc, + ItineraryStepNavigationState + >( + builder: (context, state) { + return Text( + "${state.selectedIndex} / 10", + style: TextStyle(color: Color(0xFF4A5565), fontSize: 14.sp), + ); + }, + ), + ), + body: + BlocListener< + ItineraryStepNavigationBloc, + ItineraryStepNavigationState + >( + listener: (context, state) { + _pageController.animateToPage( + state.selectedIndex, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + }, + child: Column( + children: [ + Padding( + padding: EdgeInsets.only( + left: 20.w, + right: 20.w, + bottom: 20.h, + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(10), + child: + BlocBuilder< + ItineraryStepNavigationBloc, + ItineraryStepNavigationState + >( + builder: (context, state) { + return LinearProgressIndicator( + value: state.selectedIndex / 10, + borderRadius: BorderRadius.circular(10), + backgroundColor: Colors.white, + color: const Color(0xFFF95F62), + minHeight: 6.h, + ); + }, + ), + ), + ), + + Expanded( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 20.w), + child: PageView( + controller: _pageController, + physics: const NeverScrollableScrollPhysics(), + children: [ + DateSelectionView(), + CitySelectionView(), + EnergySelectionView(), + KidsSelectionView(), + DietarySelectionView(), + ArtGallerySelectionView(), + ScenicViewpointsRatingView(), + HistoricalSiteRatingView(), + WildlifeRatingView(), + ShoppingRatingView(), + ItineraryCompletionView(), + ], + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index 2f2b1c8..263d9bc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -29,7 +29,7 @@ class MyApp extends StatelessWidget { builder: (context, child) { return MaterialApp( onGenerateRoute: _appRouter.onGenerateRoute, - initialRoute: RouteConstants.home, + initialRoute: RouteConstants.itineraryCreationStart, debugShowCheckedModeBanner: false, title: 'City Cards', theme: ThemeData(