diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 8d47715..5e32f67 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,8 @@ + + + + { mainAxisAlignment: MainAxisAlignment.center, children: [ CommonAppBar(isWhiteLogo: false, isProfilePage: false, showCart: false,), - backWidget(context, "Your Cart"), + backWidget(context, "Your Cart", Colors.black), SizedBox( height: 24.h, ), diff --git a/lib/common_packages/back_widget.dart b/lib/common_packages/back_widget.dart index 7a4d488..8a3b85f 100644 --- a/lib/common_packages/back_widget.dart +++ b/lib/common_packages/back_widget.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; -Widget backWidget(BuildContext context, String title){ +Widget backWidget(BuildContext context, String title, Color? textColor){ return Row( children: [ GestureDetector( onTap: () { Navigator.pop(context); }, - child: Icon(Icons.arrow_back, size: 24.sp), + child: Icon(Icons.arrow_back, size: 24.sp, color: textColor ?? Colors.black), ), SizedBox(width: 8.w), Text( @@ -16,6 +16,7 @@ Widget backWidget(BuildContext context, String title){ style: TextStyle( fontSize: 12.sp, fontWeight: FontWeight.w500, + color: textColor ?? Colors.black ), ), ], diff --git a/lib/common_packages/custom_bullet_points.dart b/lib/common_packages/custom_bullet_points.dart new file mode 100644 index 0000000..422db97 --- /dev/null +++ b/lib/common_packages/custom_bullet_points.dart @@ -0,0 +1,28 @@ +import 'package:citycards_customer/common_packages/custom_text.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class CustomBulletPoints extends StatelessWidget { + final Color textColor; + final String text; + + const CustomBulletPoints({ + super.key, + required this.textColor, + required this.text, + }); + + @override + Widget build(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomText(text: "•", size: 14.sp, color: textColor), + SizedBox(width: 8.w), + Expanded( + child: CustomText(text: text, color: textColor, size: 14.sp), + ), + ], + ); + } +} diff --git a/lib/core/app_router.dart b/lib/core/app_router.dart index 06af037..47a8522 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -14,10 +14,13 @@ import 'package:citycards_customer/itinerary_creation/bloc/itinerary_detail_bloc import 'package:citycards_customer/itinerary_creation/bloc/itinerary_steps_selection_bloc.dart'; import 'package:citycards_customer/itinerary_creation/views/itinerary_creation_start_view.dart'; import 'package:citycards_customer/itinerary_creation/views/itinerary_creation_view.dart'; +import 'package:citycards_customer/itinerary_creation/views/magic_itinerary_empty_view.dart'; +import 'package:citycards_customer/itinerary_creation/views/magic_itinerary_filled_view.dart'; import 'package:citycards_customer/privacy/privacy_view.dart'; import 'package:citycards_customer/search_offers/bloc/search_offers_listing_bloc.dart'; import 'package:citycards_customer/search_offers/view/search_offers_with_listing.dart'; import 'package:citycards_customer/terms_and_condition/terms_and_condition_view.dart'; +import 'package:citycards_customer/your_itinerary/view/your_itinerary_view.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../attractions/views/attractions_page_view.dart'; @@ -121,46 +124,77 @@ class AppRouter { ); case RouteConstants.attractionDetails: - return MaterialPageRoute(builder: (_) { - return AttractionDetailsView(); - }); + return MaterialPageRoute( + builder: (_) { + return AttractionDetailsView(); + }, + ); case RouteConstants.buyPass: - return MaterialPageRoute(builder: (_) { - return BuyPassView(); - }); + return MaterialPageRoute( + builder: (_) { + return BuyPassView(); + }, + ); case RouteConstants.checkout: - return MaterialPageRoute(builder: (_){ - return CheckoutView(); - }); + return MaterialPageRoute( + builder: (_) { + return CheckoutView(); + }, + ); case RouteConstants.cartPage: - return MaterialPageRoute(builder: (_){ - return MyCartPage(); - }); + return MaterialPageRoute( + builder: (_) { + return MyCartPage(); + }, + ); case RouteConstants.searchOffer: - return MaterialPageRoute(builder: (_){ - return BlocProvider( + return MaterialPageRoute( + builder: (_) { + return BlocProvider( create: (_) => OffersBloc(), - child: SearchOffersWithListing(), - ); - }); + child: SearchOffersWithListing(), + ); + }, + ); case RouteConstants.addDetails: - return MaterialPageRoute(builder: (_){ - return AddDetailsView(); - }); + return MaterialPageRoute( + builder: (_) { + return AddDetailsView(); + }, + ); case RouteConstants.createAcct: + return MaterialPageRoute( + builder: (_) { + return CreateAccountView(); + }, + ); + + case RouteConstants.yourItinerary: + return MaterialPageRoute( + builder: (_) { + return YourItineraryView(); + }, + ); + + case RouteConstants.magicItineraryEmptyScreen: return MaterialPageRoute(builder: (_){ - return CreateAccountView(); + return MagicItineraryEmptyView(); + }); + + case RouteConstants.magicItineraryFilledScreen: + return MaterialPageRoute(builder: (_){ + return MagicItineraryFilledView(); }); default: return MaterialPageRoute( builder: (_) => - const Scaffold(body: Center(child: Text('404 - Page Not Found'))), + const Scaffold(body: Center(child: Text('404 - Page Not Found'))), ); } } diff --git a/lib/core/route_constants.dart b/lib/core/route_constants.dart index 111469e..65db28c 100644 --- a/lib/core/route_constants.dart +++ b/lib/core/route_constants.dart @@ -18,8 +18,10 @@ class RouteConstants { /****************************** ITINERARY CREATION ************************************/ + static const String magicItineraryEmptyScreen = '/magicItineraryEmptyScreen'; static const String itineraryCreationStart = '/itineraryCreationStart'; static const String itineraryCreation = '/itineraryCreation'; + static const String magicItineraryFilledScreen = "/magicItineraryFilledScreen"; /**************************** ESIM Page *****************************************/ @@ -41,5 +43,6 @@ class RouteConstants { /************************** My card page ***************************************/ static const String cartPage = '/cartPage'; + static const String yourItinerary = '/yourItinerary'; } diff --git a/lib/itinerary_creation/bloc/itinerary_detail_bloc.dart b/lib/itinerary_creation/bloc/itinerary_detail_bloc.dart index 2c84e50..82aebaa 100644 --- a/lib/itinerary_creation/bloc/itinerary_detail_bloc.dart +++ b/lib/itinerary_creation/bloc/itinerary_detail_bloc.dart @@ -68,7 +68,7 @@ class ItineraryDetailState { final String? selectedCity; final String? selectedEnergy; final String? withKid; - final List? selectedDietary; + final String? selectedDietary; final String? museumRating; final String? scenicRating; final String? culturalRating; @@ -93,7 +93,7 @@ class ItineraryDetailState { String? selectedCity, String? selectedEnergy, String? withKid, - List? selectedDietary, + String? selectedDietary, String? museumRating, String? scenicRating, String? culturalRating, @@ -124,7 +124,7 @@ class AddItineraryDetailBloc selectedCity: "Paris", selectedEnergy: "", withKid: "", - selectedDietary: const [], + selectedDietary: "", museumRating: "", scenicRating: "", culturalRating: "", @@ -150,14 +150,14 @@ class AddItineraryDetailBloc }); on((event, emit) { - final currentSelection = List.from(state.selectedDietary ?? []); - - if (currentSelection.contains(event.dietary)) { - currentSelection.remove(event.dietary); - } else { - currentSelection.add(event.dietary); - } - emit(state.copyWith(selectedDietary: currentSelection)); + // final currentSelection = List.from(state.selectedDietary ?? []); + // + // if (currentSelection.contains(event.dietary)) { + // currentSelection.remove(event.dietary); + // } else { + // currentSelection.add(event.dietary); + // } + emit(state.copyWith(selectedDietary: event.dietary)); }); on((event, emit) { diff --git a/lib/itinerary_creation/bloc/itinerary_steps_selection_bloc.dart b/lib/itinerary_creation/bloc/itinerary_steps_selection_bloc.dart index 4d2b552..ecdc540 100644 --- a/lib/itinerary_creation/bloc/itinerary_steps_selection_bloc.dart +++ b/lib/itinerary_creation/bloc/itinerary_steps_selection_bloc.dart @@ -22,7 +22,7 @@ class ItineraryStepNavigationBloc on((event, emit) { final nextIndex = state.selectedIndex + 1; - if (nextIndex <= 10) { + if (nextIndex <= 11) { emit(ItineraryStepNavigationState(nextIndex)); } }); diff --git a/lib/itinerary_creation/views/itinerary_creation_start_view.dart b/lib/itinerary_creation/views/itinerary_creation_start_view.dart index 06b85de..7cbc07d 100644 --- a/lib/itinerary_creation/views/itinerary_creation_start_view.dart +++ b/lib/itinerary_creation/views/itinerary_creation_start_view.dart @@ -16,27 +16,10 @@ class ItineraryCreationStartPage extends StatelessWidget { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Container( - height: 103.h, - width: 103.w, + + Image.asset("assets/gif/create_itinerary.gif",width: 128.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), + SizedBox(height: 21.h), Text.rich( TextSpan( children: [ diff --git a/lib/itinerary_creation/views/itinerary_creation_steps/cultural_landmark_rating_view.dart b/lib/itinerary_creation/views/itinerary_creation_steps/cultural_landmark_rating_view.dart index e474c78..d745d56 100644 --- a/lib/itinerary_creation/views/itinerary_creation_steps/cultural_landmark_rating_view.dart +++ b/lib/itinerary_creation/views/itinerary_creation_steps/cultural_landmark_rating_view.dart @@ -64,18 +64,10 @@ class HistoricalSiteRatingView extends StatelessWidget { }, child: Container( padding: EdgeInsets.symmetric(horizontal: 24.w), - height: 82.h, + height: 83.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, - ), - ], + borderRadius: BorderRadius.circular(28.r), ), alignment: Alignment.center, child: Row( diff --git a/lib/itinerary_creation/views/itinerary_creation_steps/current_location_selection.dart b/lib/itinerary_creation/views/itinerary_creation_steps/current_location_selection.dart new file mode 100644 index 0000000..6280e50 --- /dev/null +++ b/lib/itinerary_creation/views/itinerary_creation_steps/current_location_selection.dart @@ -0,0 +1,172 @@ +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'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; +import 'package:geolocator/geolocator.dart'; + +class CurrentLocationSelection extends StatefulWidget { + const CurrentLocationSelection({super.key}); + + @override + State createState() => + _CurrentLocationSelectionState(); +} + +class _CurrentLocationSelectionState extends State { + final TextEditingController _controller = TextEditingController(); + LatLng? _currentLatLng; + + Future _getCurrentLocation() async { + LocationPermission permission = await Geolocator.requestPermission(); + if (permission == LocationPermission.denied || + permission == LocationPermission.deniedForever) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Location permission denied')), + ); + return; + } + + final position = await Geolocator.getCurrentPosition( + desiredAccuracy: LocationAccuracy.high, + ); + + setState(() { + _currentLatLng = LatLng(position.latitude, position.longitude); + _controller.text = + "Lat: ${position.latitude.toStringAsFixed(5)}, Lng: ${position.longitude.toStringAsFixed(5)}"; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color(0xFFFFF3F3), + body: SafeArea( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 20.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(height: 60.h), + Text( + "👋 Hello! We'd love to know more about you. Where are you visiting from", + style: TextStyle( + color: const Color(0xFF101828), + fontSize: 20.sp, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 32.h), + + SizedBox( + height: 56.h, + child: TextField( + controller: _controller, + readOnly: true, + decoration: InputDecoration( + hintText: "Search for Area, street name", + prefixIcon: Image.asset( + "assets/icons/location.png", + scale: 4, + ), + hintStyle: TextStyle( + color: Color(0xFF737373), + fontSize: 14.sp, + ), + filled: true, + fillColor: Colors.white, + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(28.r), + borderSide: BorderSide(color: Color(0xFFF95F62)), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(28.r), + borderSide: BorderSide(color: Color(0xFFF95F62)), + ), + ), + ), + ), + + SizedBox(height: 16.h), + (_currentLatLng != null) + ? ClipRRect( + borderRadius: BorderRadius.circular(16.r), + child: SizedBox( + height: 250.h, + width: double.infinity, + + child: Image.asset( + "assets/images/attra_detail_map.png", + fit: BoxFit.cover, + height: 236.h, + ), + // child: GoogleMap( + // initialCameraPosition: CameraPosition( + // target: _currentLatLng!, + // zoom: 15, + // ), + // markers: { + // Marker( + // markerId: const MarkerId("currentLocation"), + // position: _currentLatLng!, + // ), + // }, + // myLocationEnabled: true, + // myLocationButtonEnabled: false, + // ), + ), + ) + : GestureDetector( + onTap: () { + _getCurrentLocation(); + }, + child: Container( + height: 46.h, + padding: EdgeInsets.symmetric(horizontal: 12.w), + decoration: BoxDecoration( + color: Color(0xFFF95F62).withOpacity(0.4), + borderRadius: BorderRadius.circular(28.r), + border: Border.all(color: Color(0xFFF95F62)), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.my_location, + color: Color(0xFFFF7B7B), + ), + SizedBox(width: 12.w), + CustomText( + text: "Use current location", + size: 14.sp, + color: Color(0xFFF95F62), + ), + ], + ), + ), + ), + + const Spacer(), + + // --- Continue button --- + CustomFilledButton( + onTap: () { + context.read().add( + ItineraryStepNavigationNextEvent(), + ); + }, + label: "Continue", + showArrow: true, + ), + SizedBox(height: 20.h), + ], + ), + ), + ), + ); + } +} diff --git a/lib/itinerary_creation/views/itinerary_creation_steps/dietary_selection_view.dart b/lib/itinerary_creation/views/itinerary_creation_steps/dietary_selection_view.dart index 91b3ad0..96f3641 100644 --- a/lib/itinerary_creation/views/itinerary_creation_steps/dietary_selection_view.dart +++ b/lib/itinerary_creation/views/itinerary_creation_steps/dietary_selection_view.dart @@ -51,62 +51,56 @@ class _DietarySelectionViewState extends State { SizedBox(height: 38.h), SizedBox( height: 350.h, - child: - BlocBuilder< - AddItineraryDetailBloc, - ItineraryDetailState - >( - builder: (context, sate) { - return 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]; - final isSelected = (sate.selectedDietary ?? []).contains( - item['name'], - ); - return GestureDetector( - onTap: () { - context.read().add( - AddDietaryToItinerary(item['name'] ?? ""), - ); - }, - child: Container( - width: 168.w, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(24), - border: isSelected - ? Border.all(color: Color(0xFFF95F62)) - : Border.all(color: Colors.transparent), - ), - 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), - ), - ], - ), - ), + child: BlocBuilder( + builder: (context, sate) { + return 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]; + final isSelected = sate.selectedDietary == item['name']; + return GestureDetector( + onTap: () { + context.read().add( + AddDietaryToItinerary(item['name'] ?? ""), ); }, + child: Container( + width: 168.w, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(24), + border: isSelected + ? Border.all(color: Color(0xFFF95F62)) + : Border.all(color: Colors.transparent), + ), + 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), diff --git a/lib/itinerary_creation/views/itinerary_creation_steps/itinerary_completion_view.dart b/lib/itinerary_creation/views/itinerary_creation_steps/itinerary_completion_view.dart index 78a9457..6fb1f49 100644 --- a/lib/itinerary_creation/views/itinerary_creation_steps/itinerary_completion_view.dart +++ b/lib/itinerary_creation/views/itinerary_creation_steps/itinerary_completion_view.dart @@ -79,7 +79,7 @@ class ItineraryCompletionView extends StatelessWidget { ), _buildProfileRow( "Dietary", - (state.selectedDietary ?? []).join(', '), + state.selectedDietary ?? "", ), _buildProfileRow( "Museums", diff --git a/lib/itinerary_creation/views/itinerary_creation_steps/shopping_rating_view.dart b/lib/itinerary_creation/views/itinerary_creation_steps/shopping_rating_view.dart index 5c40781..c42d80b 100644 --- a/lib/itinerary_creation/views/itinerary_creation_steps/shopping_rating_view.dart +++ b/lib/itinerary_creation/views/itinerary_creation_steps/shopping_rating_view.dart @@ -65,18 +65,10 @@ class ShoppingRatingView extends StatelessWidget { }, child: Container( padding: EdgeInsets.symmetric(horizontal: 24.w), - height: 82.h, + height: 83.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, - ), - ], + borderRadius: BorderRadius.circular(28.r), ), alignment: Alignment.center, child: Row( diff --git a/lib/itinerary_creation/views/itinerary_creation_steps/wildlife_rating_view.dart b/lib/itinerary_creation/views/itinerary_creation_steps/wildlife_rating_view.dart index f6a3c83..eee0f28 100644 --- a/lib/itinerary_creation/views/itinerary_creation_steps/wildlife_rating_view.dart +++ b/lib/itinerary_creation/views/itinerary_creation_steps/wildlife_rating_view.dart @@ -50,21 +50,10 @@ class WildlifeRatingView extends StatelessWidget { }, child: Container( padding: EdgeInsets.symmetric(horizontal: 24.w), - height: 82.h, + height: 83.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, - ) - ] + color: Colors.white, + borderRadius: BorderRadius.circular(28.r), ), alignment: Alignment.center, child: Row( diff --git a/lib/itinerary_creation/views/itinerary_creation_view.dart b/lib/itinerary_creation/views/itinerary_creation_view.dart index 5adb976..75bd61d 100644 --- a/lib/itinerary_creation/views/itinerary_creation_view.dart +++ b/lib/itinerary_creation/views/itinerary_creation_view.dart @@ -1,5 +1,6 @@ import 'package:citycards_customer/itinerary_creation/bloc/itinerary_steps_selection_bloc.dart'; +import 'package:citycards_customer/itinerary_creation/views/itinerary_creation_steps/current_location_selection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; @@ -48,7 +49,7 @@ class _ItineraryCreationPageState extends State { >( builder: (context, state) { return Text( - "${state.selectedIndex} / 10", + "${state.selectedIndex} / 11", style: TextStyle(color: Color(0xFF4A5565), fontSize: 14.sp), ); }, @@ -84,7 +85,7 @@ class _ItineraryCreationPageState extends State { >( builder: (context, state) { return LinearProgressIndicator( - value: state.selectedIndex / 10, + value: state.selectedIndex / 11, borderRadius: BorderRadius.circular(10), backgroundColor: Colors.white, color: const Color(0xFFF95F62), @@ -103,7 +104,7 @@ class _ItineraryCreationPageState extends State { physics: const NeverScrollableScrollPhysics(), children: [ DateSelectionView(), - + CurrentLocationSelection(), CitySelectionView(), EnergySelectionView(), KidsSelectionView(), diff --git a/lib/itinerary_creation/views/magic_itinerary_empty_view.dart b/lib/itinerary_creation/views/magic_itinerary_empty_view.dart new file mode 100644 index 0000000..3b4362e --- /dev/null +++ b/lib/itinerary_creation/views/magic_itinerary_empty_view.dart @@ -0,0 +1,48 @@ +import 'package:citycards_customer/common_packages/app_bar.dart'; +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 MagicItineraryEmptyView extends StatelessWidget { + const MagicItineraryEmptyView({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Color(0xFFFFF5F5), + body: SafeArea( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 10.h), + child: Column( + children: [ + CommonAppBar(isWhiteLogo: false, isProfilePage: false), + SizedBox(height: 90.h), + Image.asset("assets/images/itinerary_banner.png", width: 260.w), + SizedBox(height: 27.h), + CustomText( + text: "You Don’t have an Itinerary Yet! ☹️", + size: 18.sp, + ), + SizedBox(height: 12.h,), + Text("Create your own personalized magic itinerary that suites your travel needs", + style: TextStyle( + color: Color(0xFF656565), + fontSize: 14.sp + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 27.h), + + CustomFilledButton(onTap: (){ + Navigator.pushNamed(context, RouteConstants.itineraryCreationStart); + }, + label: "Create My Itinerary", showArrow: true,) + ], + ), + ), + ), + ); + } +} diff --git a/lib/itinerary_creation/views/magic_itinerary_filled_view.dart b/lib/itinerary_creation/views/magic_itinerary_filled_view.dart new file mode 100644 index 0000000..bc67260 --- /dev/null +++ b/lib/itinerary_creation/views/magic_itinerary_filled_view.dart @@ -0,0 +1,169 @@ +import 'package:citycards_customer/common_packages/app_bar.dart'; +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:citycards_customer/postcard/widgets/dotted_border_container.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class MagicItineraryFilledView extends StatelessWidget { + const MagicItineraryFilledView({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Color(0xFFFFF5F5), + body: SafeArea( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 10.h), + child: SingleChildScrollView( + child: Column( + children: [ + CommonAppBar(isWhiteLogo: false, isProfilePage: false), + + SizedBox(height: 24.h), + ItineraryFilledCard(), + + SizedBox(height: 32.h), + + CustomPaint( + painter: DottedBorderPainter(), + child: Container( + width: double.infinity, + padding: EdgeInsets.symmetric(vertical: 24.h), + decoration: BoxDecoration( + color: Color(0xFFF95F62).withOpacity(0.25), + borderRadius: BorderRadius.circular(12.sp), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CustomText( + text: "Plan your next adventure", + color: Color(0xFF656565), + size: 14.sp, + ), + SizedBox(height: 16.h), + CustomFilledButton( + onTap: () { + Navigator.pushNamed(context, RouteConstants.itineraryCreationStart); + }, + label: "Create My Itinerary", + showArrow: true, + ), + ], + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} + +class ItineraryFilledCard extends StatelessWidget { + const ItineraryFilledCard({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 8.h), + decoration: BoxDecoration( + border: Border.all(color: Colors.black.withOpacity(0.12)), + borderRadius: BorderRadius.circular(12.r), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomText( + text: "Melbourne Unlimited Card", + size: 16.sp, + weight: FontWeight.w500, + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 2.h), + decoration: BoxDecoration( + color: Color(0xFF439F6E), + borderRadius: BorderRadius.circular(100.r), + ), + child: CustomText( + text: "Active", + size: 11.sp, + color: Colors.white, + ), + ), + ], + ), + SizedBox(height: 4.h), + + CustomText( + text: "Melbourne", + size: 12.sp, + color: Colors.black.withOpacity(0.4), + ), + SizedBox(height: 12.h), + Row( + children: [ + Image.asset("assets/icons/calender_filled.png", width: 16.sp), + SizedBox(width: 4.w), + CustomText(text: "7 days", color: Color(0xFF8E8E8E), size: 12.sp), + ], + ), + SizedBox(height: 8.h), + Row( + children: [ + Icon( + Icons.location_on_rounded, + color: Color(0xFF8E8E8E), + size: 16.sp, + ), + SizedBox(width: 4.w), + CustomText( + text: "6 attractions", + color: Color(0xFF8E8E8E), + size: 12.sp, + ), + ], + ), + SizedBox(height: 8.h), + + Row( + children: [ + Icon(Icons.watch_later, color: Color(0xFF8E8E8E), size: 16.sp), + SizedBox(width: 4.w), + CustomText( + text: "Created 1/15/2024", + color: Color(0xFF8E8E8E), + size: 12.sp, + ), + ], + ), + + SizedBox(height: 12.h), + + Container( + height: 43.h, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.r), + border: Border.all(color: Color(0xFFF95F62)), + ), + child: Center( + child: CustomText( + text: "View Itinerary", + size: 16.sp, + color: Color(0xFFF95F62), + weight: FontWeight.w500, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index be24e47..88f2261 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.buyPass, + initialRoute: RouteConstants.magicItineraryFilledScreen, debugShowCheckedModeBanner: false, title: 'City Cards', theme: ThemeData( diff --git a/lib/postcard/widgets/dotted_border_container.dart b/lib/postcard/widgets/dotted_border_container.dart index 7165fed..23e3d45 100644 --- a/lib/postcard/widgets/dotted_border_container.dart +++ b/lib/postcard/widgets/dotted_border_container.dart @@ -12,9 +12,7 @@ class DottedBorderContainer extends StatelessWidget { height: 300.h, width: double.infinity, alignment: Alignment.center, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - ), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(16)), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -24,7 +22,11 @@ class DottedBorderContainer extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.add_circle_outline, color: Color(0xffF95F62), size: 25,), + Icon( + Icons.add_circle_outline, + color: Color(0xffF95F62), + size: 25, + ), const Text( "Add image", style: TextStyle( @@ -52,18 +54,18 @@ class DottedBorderPainter extends CustomPainter { const double dashWidth = 6; const double dashSpace = 3; final path = Path() - ..addRRect(RRect.fromRectAndRadius( + ..addRRect( + RRect.fromRectAndRadius( Rect.fromLTWH(0, 0, size.width, size.height), - const Radius.circular(16))); + const Radius.circular(16), + ), + ); final pathMetrics = path.computeMetrics(); for (final metric in pathMetrics) { double distance = 0.0; while (distance < metric.length) { - final segment = metric.extractPath( - distance, - distance + dashWidth, - ); + final segment = metric.extractPath(distance, distance + dashWidth); canvas.drawPath(segment, paint); distance += dashWidth + dashSpace; } diff --git a/lib/your_itinerary/bloc/itinerary_days_tabs_bloc.dart b/lib/your_itinerary/bloc/itinerary_days_tabs_bloc.dart new file mode 100644 index 0000000..1d9492b --- /dev/null +++ b/lib/your_itinerary/bloc/itinerary_days_tabs_bloc.dart @@ -0,0 +1,24 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; + +abstract class YourItineraryDayTab {} + +class ChangeItineraryDayTabEvent extends YourItineraryDayTab { + final int? tabIndex; + + ChangeItineraryDayTabEvent(this.tabIndex); +} + +class ItineraryDayTabState { + final int? tabIndex; + + ItineraryDayTabState(this.tabIndex); +} + +class ItineraryChangeDayTabBloc + extends Bloc { + ItineraryChangeDayTabBloc() : super(ItineraryDayTabState(0)) { + on((event, emit) { + emit(ItineraryDayTabState(event.tabIndex)); + }); + } +} diff --git a/lib/your_itinerary/bloc/your_itinerary_tab_bloc.dart b/lib/your_itinerary/bloc/your_itinerary_tab_bloc.dart new file mode 100644 index 0000000..2b53850 --- /dev/null +++ b/lib/your_itinerary/bloc/your_itinerary_tab_bloc.dart @@ -0,0 +1,24 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; + +abstract class YourItineraryTab {} + +class ChangeItineraryTabEvent extends YourItineraryTab { + final int? tabIndex; + + ChangeItineraryTabEvent(this.tabIndex); +} + +class ItineraryTabState { + final int? tabIndex; + + ItineraryTabState(this.tabIndex); +} + +class ItineraryChangeTabBloc + extends Bloc { + ItineraryChangeTabBloc() : super(ItineraryTabState(0)) { + on((event, emit) { + emit(ItineraryTabState(event.tabIndex)); + }); + } +} diff --git a/lib/your_itinerary/view/your_itinerary_view.dart b/lib/your_itinerary/view/your_itinerary_view.dart new file mode 100644 index 0000000..fb74d60 --- /dev/null +++ b/lib/your_itinerary/view/your_itinerary_view.dart @@ -0,0 +1,371 @@ +import 'package:citycards_customer/common_packages/app_bar.dart'; +import 'package:citycards_customer/common_packages/custom_text.dart'; +import 'package:citycards_customer/your_itinerary/bloc/itinerary_days_tabs_bloc.dart'; +import 'package:citycards_customer/your_itinerary/bloc/your_itinerary_tab_bloc.dart'; +import 'package:citycards_customer/your_itinerary/widgets/itinerary_card_widget.dart'; +import 'package:citycards_customer/your_itinerary/widgets/itinerary_tab_button.dart'; +import 'package:citycards_customer/your_itinerary/widgets/summary_card_view.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class YourItineraryView extends StatelessWidget { + const YourItineraryView({super.key}); + + @override + Widget build(BuildContext context) { + return MultiBlocProvider( + providers: [ + BlocProvider(create: (_) => ItineraryChangeTabBloc()), + BlocProvider(create: (_) => ItineraryChangeDayTabBloc()), + ], + child: Scaffold( + backgroundColor: Colors.white, + body: SafeArea( + child: SingleChildScrollView( + child: Column( + children: [ + Stack( + children: [ + Image.asset( + "assets/images/trump_house.png", + height: 155.h, + width: double.infinity, + fit: BoxFit.cover, + alignment: Alignment.topCenter, + ), + Positioned.fill( + child: Container(color: Colors.black.withOpacity(0.3)), + ), + Positioned( + top: 20.h, + left: 20.w, + right: 20.w, + child: Column( + children: [ + CommonAppBar(isWhiteLogo: true, isProfilePage: false), + SizedBox(height: 5.h), + Divider( + height: 0.4.h, + color: Colors.white.withOpacity(.3), + ), + SizedBox(height: 26.h), + Row( + children: [ + GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: Icon( + Icons.arrow_back, + size: 24.sp, + color: Colors.white, + ), + ), + SizedBox(width: 8.w), + Text( + "Melbourne Itinerary", + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + ], + ), + ], + ), + ), + ], + ), + SizedBox(height: 12.h), + + Padding( + padding: EdgeInsets.symmetric(horizontal: 20.w), + child: Row( + children: [ + CustomText( + text: "Melbourne", + size: 24.sp, + weight: FontWeight.w500, + ), + const Spacer(), + Icon(Icons.edit, color: Color(0xFFF95F62), size: 16.sp), + SizedBox(width: 24.w), + Icon(Icons.share, color: Color(0xFFF95F62), size: 16.sp), + SizedBox(width: 24.w), + Icon( + Icons.file_download_outlined, + color: Color(0xFFF95F62), + size: 20.sp, + ), + ], + ), + ), + + Padding( + padding: EdgeInsets.symmetric(horizontal: 20.w), + child: Row( + children: [ + Image.asset( + "assets/icons/calender_filled.png", + width: 14.sp, + color: Color(0xFF8E8E8E), + ), + SizedBox(width: 4.w), + CustomText( + text: "22/02/2025", + size: 12.sp, + color: Color(0xFF8E8E8E), + ), + SizedBox(width: 12.w), + + Image.asset( + "assets/icons/adult.png", + width: 14.sp, + color: Color(0xFF8E8E8E), + ), + SizedBox(width: 4.w), + CustomText( + text: "3 adults", + size: 12.sp, + color: Color(0xFF8E8E8E), + ), + SizedBox(width: 12.w), + + Image.asset( + "assets/icons/kid.png", + height: 14.sp, + color: Color(0xFF8E8E8E), + ), + SizedBox(width: 4.w), + CustomText( + text: "3 kids", + size: 12.sp, + color: Color(0xFF8E8E8E), + ), + SizedBox(width: 12.w), + ], + ), + ), + SizedBox(height: 25.h), + Padding( + padding: EdgeInsets.symmetric(horizontal: 20.w), + child: Container( + height: 50.h, + padding: EdgeInsets.symmetric( + vertical: 4.h, + horizontal: 4.w, + ), + decoration: BoxDecoration( + color: Color(0xFFFEE7E7), + borderRadius: BorderRadius.circular(100.r), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ItineraryTabButton(index: 0, label: "Daily View"), + ItineraryTabButton(index: 1, label: "Summary"), + ], + ), + ), + ), + SizedBox(height: 25.h), + + BlocBuilder( + builder: (context, state) { + if (state.tabIndex == 0) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 20.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + ...List.generate(4, (index) { + return _DayTabButton( + index: index, + label: "Day ${index + 1}", + ); + }), + ], + ), + ), + SizedBox(height: 30.h), + + Container( + height: 70.h, + width: double.infinity, + padding: EdgeInsets.symmetric( + horizontal: 8.w, + vertical: 8.h, + ), + decoration: BoxDecoration( + color: Color(0xFF000000).withOpacity(0.04), + borderRadius: BorderRadius.circular(12.r), + border: Border.all( + color: Color(0xFFF95F62).withOpacity(0.12), + ), + ), + child: Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(8.r), + child: Image.asset( + "assets/images/trump_house.png", + width: 54.w, + height: 54.h, + fit: BoxFit.cover, + ), + ), + SizedBox(width: 24.w), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + CustomText( + text: "Melbourne, Australia", + size: 18.sp, + weight: FontWeight.w500, + ), + SizedBox(height: 4.h), + CustomText( + text: "18°C, Sunny", + size: 12, + weight: FontWeight.w500, + color: Color(0xFFFFB23F), + ), + ], + ), + ], + ), + ), + + SizedBox(height: 25.h), + Align( + alignment: Alignment.centerLeft, + child: CustomText( + text: "GMT", + size: 12.sp, + weight: FontWeight.w500, + color: Colors.black.withOpacity(0.7), + ), + ), + SizedBox(height: 25.h), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + CustomText( + text: "8:00 am", + size: 14.sp, + color: Color(0xFF8E8E8E), + ), + SizedBox(width: 26.w), + Expanded( + child: Divider( + height: 1, + color: Colors.black.withOpacity(0.2), + ), + ), + ], + ), + SizedBox(height: 20.h), + + Column( + children: List.generate( + 3, + (index) => ItineraryVisitingPlaceCard( + time: "9:00 am", + image: "assets/images/itinerary_card.png", + title: "Ibis Paris Montmartre Sacré-Coeur", + subtitle: + "5 Rue Caulaincourt, 75018 Paris France", + amenities: [ + "Food", + "Drinks", + "Culture", + "Souvenirs", + ], + points: [ + "Coffee at Pellegrini’s Espresso Bar (iconic old-school spot)", + "Try the famous hot jam doughnuts", + "Shop for fresh produce in the Dairy Hall", + "Pick up unique souvenirs in the General Merchandise section", + "Join a guided history tour of the market", + ], dayIndex: 0, + ), + ), + ), + ], + ), + ); + } else { + /// Summary Tab + return Padding( + padding: EdgeInsets.symmetric(horizontal: 20.w), + child: Column( + children: [ + SummaryCard(day: "Day 1", date: "20/09/2024"), + SummaryCard(day: "Day 2", date: "21/09/2024"), + SummaryCard(day: "Day 3", date: "22/09/2024"), + ], + ), + ); + } + }, + ), + ], + ), + ), + ), + ), + ); + } +} + +class _DayTabButton extends StatelessWidget { + final int index; + final String label; + const _DayTabButton({required this.index, required this.label}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + final isActive = state.tabIndex == index; + return GestureDetector( + onTap: () { + context.read().add( + ChangeItineraryDayTabEvent(index), + ); + }, + child: Container( + width: MediaQuery.of(context).size.width * 0.224, + padding: EdgeInsets.symmetric(vertical: 11.h), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: isActive + ? Color(0xFF007AFF) + : Colors.black.withOpacity(0.2), + ), + ), + ), + child: Center( + child: Text( + label, + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + color: isActive ? Color(0xFF007AFF) : Color(0xFF8E8E8E), + ), + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/your_itinerary/widgets/itinerary_card_widget.dart b/lib/your_itinerary/widgets/itinerary_card_widget.dart new file mode 100644 index 0000000..8800cc4 --- /dev/null +++ b/lib/your_itinerary/widgets/itinerary_card_widget.dart @@ -0,0 +1,105 @@ +import 'package:citycards_customer/common_packages/custom_bullet_points.dart'; +import 'package:citycards_customer/common_packages/custom_text.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class ItineraryVisitingPlaceCard extends StatelessWidget { + final String time; + final int dayIndex; + final String image; + final String title; + final String subtitle; + final List amenities; + final List points; + + const ItineraryVisitingPlaceCard({ + required this.dayIndex, + required this.image, + required this.title, + required this.subtitle, + required this.amenities, + required this.points, + required this.time, + }); + + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.only(bottom: 20.h), + color: Colors.white, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + CustomText(text: time, size: 14.sp, color: Color(0xFF8E8E8E)), + SizedBox(width: 26.w), + Expanded( + child: Divider(height: 1, color: Colors.black.withOpacity(0.2)), + ), + ], + ), + + SizedBox(height: 4.h), + ClipRRect( + borderRadius: BorderRadius.circular(8.r), + child: Image.asset( + image, + width: double.infinity, + fit: BoxFit.cover, + ), + ), + SizedBox(height: 6.h), + Text( + title, + style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w500), + ), + SizedBox(height: 4.h), + Text( + subtitle, + style: TextStyle( + fontSize: 12.sp, + color: Color(0xFF4E4E4E), + fontWeight: FontWeight.w500, + ), + ), + SizedBox(height: 12.h), + + Row( + children: [ + ...List.generate(4, (index) { + return Container( + margin: EdgeInsets.only(right: 8.w), + padding: EdgeInsets.symmetric( + vertical: 6.h, + horizontal: 12.w, + ), + decoration: BoxDecoration( + color: Color(0xFFFEE7E7), + border: Border.all(color: Color(0xFFFDCDCE)), + borderRadius: BorderRadius.circular(100.r), + ), + child: Center( + child: CustomText( + text: amenities[index], + color: Color(0xFFBB474A), + size: 14.sp, + ), + ), + ); + }), + ], + ), + SizedBox(height: 12.h), + ...List.generate(points.length, (index) { + return CustomBulletPoints( + textColor: Colors.black.withOpacity(0.6), + text: points[index], + ); + }), + ], + ), + ); + } +} diff --git a/lib/your_itinerary/widgets/itinerary_tab_button.dart b/lib/your_itinerary/widgets/itinerary_tab_button.dart new file mode 100644 index 0000000..88f9cce --- /dev/null +++ b/lib/your_itinerary/widgets/itinerary_tab_button.dart @@ -0,0 +1,46 @@ +import 'package:citycards_customer/your_itinerary/bloc/your_itinerary_tab_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class ItineraryTabButton extends StatelessWidget { + final int index; + final String label; + + const ItineraryTabButton({required this.index, required this.label}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + final isActive = state.tabIndex == index; + return GestureDetector( + onTap: () { + context + .read() + .add(ChangeItineraryTabEvent(index)); + }, + child: Container( + width: MediaQuery.of(context).size.width * 0.43, + decoration: BoxDecoration( + color: isActive + ? Colors.white + : Colors.transparent, + borderRadius: BorderRadius.circular(100.r), + ), + child: Center( + child: Text( + label, + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + color:Colors.black + ), + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/your_itinerary/widgets/summary_card_view.dart b/lib/your_itinerary/widgets/summary_card_view.dart new file mode 100644 index 0000000..ee538aa --- /dev/null +++ b/lib/your_itinerary/widgets/summary_card_view.dart @@ -0,0 +1,154 @@ +import 'package:citycards_customer/common_packages/custom_bullet_points.dart'; +import 'package:citycards_customer/common_packages/custom_expansion_tile.dart'; +import 'package:citycards_customer/common_packages/custom_filled_button.dart'; +import 'package:citycards_customer/common_packages/custom_text.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class SummaryCard extends StatelessWidget { + final String day; + final String date; + + SummaryCard({required this.day, required this.date}); + + List> itineraryStops = [ + { + "title": "9:00 am: Pallegrini Expresso Bar", + "details": [ + "Coffee at Pellegrini’s Espresso Bar (iconic old-school spot)", + "Try the famous hot jam doughnuts", + "Shop for fresh produce in the Dairy Hall", + "Pick up unique souvenirs in the General Merchandise section", + "Join a guided history tour of the market", + ], + }, + { + "title": "9:00 am: Pallegrini Expresso Bar", + "details": [ + "Coffee at Pellegrini’s Espresso Bar (iconic old-school spot)", + "Try the famous hot jam doughnuts", + "Shop for fresh produce in the Dairy Hall", + "Pick up unique souvenirs in the General Merchandise section", + "Join a guided history tour of the market", + ], + }, + { + "title": "9:00 am: Pallegrini Expresso Bar", + "details": [ + "Coffee at Pellegrini’s Espresso Bar (iconic old-school spot)", + "Try the famous hot jam doughnuts", + "Shop for fresh produce in the Dairy Hall", + "Pick up unique souvenirs in the General Merchandise section", + "Join a guided history tour of the market", + ], + }, + ]; + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + margin: EdgeInsets.only(bottom: 20.h), + padding: EdgeInsets.symmetric(vertical: 12.w, horizontal: 8.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10.r), + color: Colors.white, + border: Border.all(color: Color(0xFFF95F62)), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + CustomText( + text: "${day} :", + size: 16.sp, + weight: FontWeight.w500, + color: Color(0xFF212121), + ), + SizedBox(width: 16.w), + Row( + children: [ + Image.asset( + "assets/icons/calender_filled.png", + color: Color(0xFFF95F62), + width: 20.sp, + ), + SizedBox(width: 4.w), + CustomText( + text: date, + color: Color(0xfFF95F62), + size: 16.sp, + weight: FontWeight.w500, + ), + ], + ), + ], + ), + SizedBox(height: 15.h), + + ...List.generate(itineraryStops.length, (index) { + final item = itineraryStops[index]; + return Padding( + padding: EdgeInsets.symmetric(vertical: 5.h), + child: CustomExpansionTile( + borderRadius: BorderRadius.circular(5.r), + dense: true, + visualDensity: VisualDensity.compact, + backgroundColor: Color(0xFFFEE7E7), + collapsedBackgroundColor: Color(0xFFFEE7E7), + tilePadding: EdgeInsets.symmetric( + horizontal: 12.w, + vertical: 0, + ), + childrenPadding: EdgeInsets.fromLTRB(20.w, 0, 20.w, 12.h), + title: Text( + item['title'], + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w500, + color: Colors.black87, + ), + ), + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...item['details'].map( + (e) => CustomBulletPoints( + textColor: Color(0xFF5C5C5C), + text: e, + ), + ), + SizedBox(height: 10.h), + Container( + height: 32.h, + width: 124.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(100.r), + color: Color(0xFFF95F62), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Image.asset("assets/icons/location.png",color: Colors.white,width: 14.sp), + SizedBox(width: 6.w,), + CustomText( + text: "Get Directions", + size: 11.sp, + color: Colors.white, + ), + ], + ), + ), + ], + ), + ], + ), + ); + }), + ], + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index efe589c..728c299 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -9,6 +9,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.7" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" async: dependency: transitive description: @@ -73,6 +81,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" cupertino_icons: dependency: "direct main" description: @@ -81,6 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + dbus: + dependency: transitive + description: + name: dbus + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" + url: "https://pub.dev" + source: hosted + version: "0.7.11" fake_async: dependency: transitive description: @@ -129,6 +153,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.9.3+4" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" flutter: dependency: "direct main" description: flutter @@ -184,6 +216,70 @@ packages: description: flutter source: sdk version: "0.0.0" + geoclue: + dependency: transitive + description: + name: geoclue + sha256: c2a998c77474fc57aa00c6baa2928e58f4b267649057a1c76738656e9dbd2a7f + url: "https://pub.dev" + source: hosted + version: "0.1.1" + geolocator: + dependency: "direct main" + description: + name: geolocator + sha256: "79939537046c9025be47ec645f35c8090ecadb6fe98eba146a0d25e8c1357516" + url: "https://pub.dev" + source: hosted + version: "14.0.2" + geolocator_android: + dependency: transitive + description: + name: geolocator_android + sha256: "179c3cb66dfa674fc9ccbf2be872a02658724d1c067634e2c427cf6df7df901a" + url: "https://pub.dev" + source: hosted + version: "5.0.2" + geolocator_apple: + dependency: transitive + description: + name: geolocator_apple + sha256: dbdd8789d5aaf14cf69f74d4925ad1336b4433a6efdf2fce91e8955dc921bf22 + url: "https://pub.dev" + source: hosted + version: "2.3.13" + geolocator_linux: + dependency: transitive + description: + name: geolocator_linux + sha256: c4e966f0a7a87e70049eac7a2617f9e16fd4c585a26e4330bdfc3a71e6a721f3 + url: "https://pub.dev" + source: hosted + version: "0.2.3" + geolocator_platform_interface: + dependency: transitive + description: + name: geolocator_platform_interface + sha256: "30cb64f0b9adcc0fb36f628b4ebf4f731a2961a0ebd849f4b56200205056fe67" + url: "https://pub.dev" + source: hosted + version: "4.2.6" + geolocator_web: + dependency: transitive + description: + name: geolocator_web + sha256: b1ae9bdfd90f861fde8fd4f209c37b953d65e92823cb73c7dee1fa021b06f172 + url: "https://pub.dev" + source: hosted + version: "4.1.3" + geolocator_windows: + dependency: transitive + description: + name: geolocator_windows + sha256: "175435404d20278ffd220de83c2ca293b73db95eafbdc8131fe8609be1421eb6" + url: "https://pub.dev" + source: hosted + version: "0.2.5" google_fonts: dependency: "direct main" description: @@ -192,6 +288,70 @@ packages: url: "https://pub.dev" source: hosted version: "6.3.2" + google_maps: + dependency: transitive + description: + name: google_maps + sha256: "5d410c32112d7c6eb7858d359275b2aa04778eed3e36c745aeae905fb2fa6468" + url: "https://pub.dev" + source: hosted + version: "8.2.0" + google_maps_flutter: + dependency: "direct main" + description: + name: google_maps_flutter + sha256: c389e16fafc04b37a4105e0757ecb9d59806026cee72f408f1ba68811d01bfe6 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + google_maps_flutter_android: + dependency: transitive + description: + name: google_maps_flutter_android + sha256: f820a3990d4ff23e3baf01ce794f7f08cca9a9ce6c875ec96882d605f6f039df + url: "https://pub.dev" + source: hosted + version: "2.18.4" + google_maps_flutter_ios: + dependency: transitive + description: + name: google_maps_flutter_ios + sha256: ca02463b19a9abc7d31fcaf22631d021d647107467f741b917a69fa26659fd75 + url: "https://pub.dev" + source: hosted + version: "2.15.5" + google_maps_flutter_platform_interface: + dependency: transitive + description: + name: google_maps_flutter_platform_interface + sha256: f4b9b44f7b12a1f6707ffc79d082738e0b7e194bf728ee61d2b3cdf5fdf16081 + url: "https://pub.dev" + source: hosted + version: "2.14.0" + google_maps_flutter_web: + dependency: transitive + description: + name: google_maps_flutter_web + sha256: "53e5dbf73ff04153acc55a038248706967c21d5b6ef6657a57fce2be73c2895a" + url: "https://pub.dev" + source: hosted + version: "0.5.14+2" + gsettings: + dependency: transitive + description: + name: gsettings + sha256: "1b0ce661f5436d2db1e51f3c4295a49849f03d304003a7ba177d01e3a858249c" + url: "https://pub.dev" + source: hosted + version: "0.2.8" + html: + dependency: transitive + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.dev" + source: hosted + version: "0.15.6" http: dependency: transitive description: @@ -360,6 +520,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + package_info_plus: + dependency: transitive + description: + name: package_info_plus + sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968" + url: "https://pub.dev" + source: hosted + version: "8.3.1" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" + url: "https://pub.dev" + source: hosted + version: "3.2.1" path: dependency: transitive description: @@ -456,6 +632,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.5+1" + sanitize_html: + dependency: transitive + description: + name: sanitize_html + sha256: "12669c4a913688a26555323fb9cec373d8f9fbe091f2d01c40c723b33caa8989" + url: "https://pub.dev" + source: hosted + version: "2.1.0" sky_engine: dependency: transitive description: flutter @@ -469,6 +653,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" stack_trace: dependency: transitive description: @@ -485,6 +677,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" string_scanner: dependency: transitive description: @@ -517,6 +717,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + uuid: + dependency: transitive + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" vector_math: dependency: transitive description: @@ -541,6 +749,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + win32: + dependency: transitive + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" + source: hosted + version: "5.15.0" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f07bf2b..7cfb73f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,6 +41,8 @@ dependencies: image_picker: ^1.2.0 image: ^4.5.4 flutter_otp_text_field: ^1.5.1+1 + google_maps_flutter: ^2.13.1 + geolocator: ^14.0.2 dev_dependencies: flutter_test: @@ -70,6 +72,7 @@ flutter: - assets/images/ - assets/icons/ - assets/dummy/ + - assets/gif/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/to/resolution-aware-images