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 @@ + + + + { crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ - CommonAppBar(isWhiteLogo: false, isProfilePage: false, showCart: false,), - backWidget(context, "Your Cart"), - SizedBox( - height: 24.h, + CommonAppBar( + isWhiteLogo: false, + isProfilePage: false, + showCart: false, ), + backWidget(context, "Your Cart", Colors.black), + SizedBox(height: 24.h), Container( padding: EdgeInsets.all(4.0), margin: const EdgeInsets.all(16), @@ -60,7 +61,7 @@ class _MyCartPageState extends State { child: selectedTab == 0 ? const MyPassesPage() : const MyPostCardsPage(), - ) + ), ], ), ], diff --git a/lib/cart/views/my_pass_page_view.dart b/lib/cart/views/my_pass_page_view.dart index 4160820..f9bb37a 100644 --- a/lib/cart/views/my_pass_page_view.dart +++ b/lib/cart/views/my_pass_page_view.dart @@ -1,4 +1,8 @@ import 'package:citycards_customer/cart/views/view_pass_page_view.dart'; +import 'package:citycards_customer/checkout/widget/all_coupons_bottomsheet.dart'; +import 'package:citycards_customer/common_packages/custom_dashed_line.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_bloc/flutter_bloc.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; @@ -14,160 +18,354 @@ class MyPassesPage extends StatelessWidget { if (state is PassLoading) { return const Center(child: CircularProgressIndicator()); } else if (state is PassLoaded) { - final pass = state.passes.first; - return SingleChildScrollView( - - child: Column( - children: [ - GestureDetector( - onTap: () => Navigator.push( - context, - MaterialPageRoute(builder: (_) => const ViewPassPage()), + return + Column( + children: [ + SizedBox(height: 22.h), + Container( + decoration: BoxDecoration( + color: Colors.white, + border: Border.all( + color: Color(0xFFF95FAF).withOpacity(0.2), ), - child: Container( - height: 130.h, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - border: Border.all(color: const Color(0xffF1F5F7)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.1), - blurRadius: 6, - offset: const Offset(0, 3), - ), - ], - ), - child: Row( - children: [ - ClipRRect( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(16), - bottomLeft: Radius.circular(16)), - child: Image.asset( - pass.imageUrl, - width: 120.w, - height: 130.h, - fit: BoxFit.cover, + borderRadius: BorderRadius.circular(8.r), + ), + child: Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(8.r), + bottomLeft: Radius.circular(8.r), + ), + child: Image.asset( + "assets/images/card_banner.png", + scale: 4, + width: 105.w, + height: 123.h, + fit: BoxFit.cover, + ), ), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.all(12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(pass.title, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16)), - const SizedBox(height: 4), - Text(pass.duration), - Row( + SizedBox(width: 6.66.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomText( + text: "Melbourne", + weight: FontWeight.w500, + size: 16.sp, + ), + SizedBox(height: 5.h), + CustomText( + text: "2 Days", + color: Color(0xFF8E8E8E), + size: 12.sp, + ), + SizedBox(height: 5.h), + + SizedBox( + width: MediaQuery.of(context).size.width * .5, + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, children: [ - const Icon(Icons.person, size: 10,), - Text(" ${pass.adults} adults "), - Text("Qty: ${pass.quantity}"), + Row( + children: [ + Image.asset( + 'assets/icons/adult.png', + scale: 4, + ), + SizedBox(width: 4.w), + CustomText( + text: "3 adults", + color: Color(0xFF8E8E8E), + size: 12.sp, + ), + ], + ), + + Row( + children: [ + Image.asset( + 'assets/icons/qty.png', + scale: 4, + ), + SizedBox(width: 4.w), + Text.rich( + TextSpan( + children: [ + TextSpan( + text: "Qty:", + style: TextStyle( + color: Color(0xFF8E8E8E), + fontSize: 12.sp, + ), + ), + TextSpan( + text: " 2", + style: TextStyle( + color: Color(0xFF000000), + fontSize: 12.sp, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + ], + ), ], ), - ], + ), + + SizedBox(height: 5.h), + Row( + children: [ + Image.asset( + "assets/icons/kid.png", + scale: 4, + ), + SizedBox(width: 4.w), + CustomText( + text: "3 Kids", + color: Color(0xFF8E8E8E), + size: 12.sp, + ), + + SizedBox(width: 53.w), + + CustomText( + text: "\$49.50", + size: 24.sp, + weight: FontWeight.w500, + color: Color(0xFFF95F62), + ), + ], + ), + ], + ), + ], + ), + + Container( + width: 35.w, + height: 123.h, + decoration: BoxDecoration( + color: Color(0xFFF95FAF), + borderRadius: BorderRadius.only( + bottomRight: Radius.circular(8.r), + topRight: Radius.circular(8.r), + ), + ), + child: RotatedBox( + quarterTurns: -1, + child: Center( + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: "Flexi ", + style: TextStyle( + color: Colors.white, + fontSize: 16.sp, + ), + ), + TextSpan( + text: "Card", + style: TextStyle( + color: Colors.white, + fontSize: 12.sp, + ), + ), + ], + ), ), ), ), + ), + ], + ), + ), + ), + + SizedBox(height: 15.h), + Container( + padding: EdgeInsets.symmetric( + horizontal: 12.w, + vertical: 12.h, + ), + decoration: BoxDecoration( + color: Color(0xFFFFF5F5), + borderRadius: BorderRadius.circular(8.r), + border: Border.all( + color: Color(0xFFBB474A).withOpacity(0.4), + width: 0.8, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomText( + text: "Get 10% off on your first trip", + color: Color(0xFF262626), + size: 14.sp, + ), + SizedBox(height: 7.h), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + GestureDetector( + onTap: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(12.r), + ), + ), + builder: (_) => AllCouponsBottomsheet(), + ); + }, + child: CustomText( + text: "View all coupons", + color: Color(0xFFF95F62), + size: 12, + ), + ), + SizedBox(width: 3.w), + Icon(Icons.arrow_right, color: Color(0xFFF95F62)), + ], + ), + ], + ), + + const Spacer(), + Container( + padding: EdgeInsets.symmetric( + horizontal: 20.w, + vertical: 10.h, + ), + decoration: BoxDecoration( + border: Border.all(color: Color(0xFFF95F62)), + borderRadius: BorderRadius.circular(8.r), + ), + child: CustomText( + text: "Apply", + color: Color(0xFFF95F62), + size: 14.sp, + ), + ), + ], + ), + ), + + SizedBox(height: 15.h), + + DashedDivider( + color: Color(0xFFACACAC), + thickness: 1.h, + dashLength: 4, + dashSpace: 4, + ), + SizedBox(height: 10.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomText(text: "Subtotal", size: 14.sp), + CustomText( + text: "\$49.50", + size: 14.sp, + weight: FontWeight.w500, + ), + ], + ), + SizedBox(height: 14.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomText(text: "Discount", size: 14.sp), + CustomText( + text: "-7.20%", + size: 14.sp, + weight: FontWeight.w500, + ), + ], + ), + SizedBox(height: 10.h), + DashedDivider( + color: Color(0xFFACACAC), + thickness: 1.h, + dashLength: 4, + dashSpace: 4, + ), + SizedBox(height: 10.h), + + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomText(text: 'Total', size: 14.sp), + SizedBox(height: 4.h), + CustomText( + text: "Including \$2.24 in taxes", + size: 12.sp, + color: Colors.black.withOpacity(0.6), + ), ], ), ), - ), - const SizedBox(height: 20), - _couponBox(), - const SizedBox(height: 20), - _summaryBox(state), - const SizedBox(height: 30), - _checkoutButton(), - ], - ), + CustomText( + text: "\$42.60", + size: 24.sp, + weight: FontWeight.w500, + ), + ], + ), + + SizedBox(height: 150.h,), + + CustomFilledButton( + onTap: () {}, + width: double.infinity, + label: "Proceed to Checkout", + ), + SizedBox(height: 25.h), + ], ); } - return const SizedBox.shrink(); + return Center( + child: Column( + children: [ + Image.asset("assets/gif/empty_cart.gif", width: 250.w), + CustomText( + text: "You do not have any passes", + size: 24.sp, + color: Color(0xFFF95F62), + ), + SizedBox(height: 4.h), + Text( + "Get a pass and get offers and discounts and more on your trip to your favourite city", + style: TextStyle(color: Color(0xFF656565), fontSize: 14.sp), + textAlign: TextAlign.center, + ), + ], + ), + ); }, ); } - Widget _couponBox() { - return Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: const Color(0xffFFF5F5), - ), - child: Row( - children: [ - const Expanded( - child: Text("Get 10% off on your first trip", - style: TextStyle(fontWeight: FontWeight.w500))), - Container( - padding: - const EdgeInsets.symmetric(horizontal: 20, vertical: 8), - decoration: BoxDecoration( - color: Colors.white, - border: Border.all(color: Colors.redAccent), - borderRadius: BorderRadius.circular(8), - ), - child: const Text("Apply", - style: TextStyle(color: Colors.redAccent)), - ) - ], - ), - ); - } - Widget _summaryBox(PassLoaded state) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _row("Subtotal", "\$${state.subtotal.toStringAsFixed(2)}"), - _row("Discount", "-${state.discountPercent.toStringAsFixed(2)}%"), - const Divider(), - _row("Total", "\$${state.total.toStringAsFixed(2)}", - bold: true, large: true), - const Text("Including \$2.24 in taxes", - style: TextStyle(color: Colors.black54, fontSize: 12)), - ], - ); - } - Widget _row(String label, String value, - {bool bold = false, bool large = false}) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(label, - style: TextStyle( - fontWeight: bold ? FontWeight.bold : FontWeight.normal, - fontSize: large ? 18 : 14)), - Text(value, - style: TextStyle( - fontWeight: bold ? FontWeight.bold : FontWeight.normal, - fontSize: large ? 18 : 14)), - ], - ), - ); - } - - Widget _checkoutButton() { - return Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(vertical: 14), - decoration: BoxDecoration( - color: Colors.redAccent, - borderRadius: BorderRadius.circular(30), - ), - child: const Center( - child: Text("Proceed to Checkout", - style: TextStyle(color: Colors.white, fontSize: 16)), - ), - ); - } } diff --git a/lib/cart/views/my_postcard_page_view.dart b/lib/cart/views/my_postcard_page_view.dart index f913697..4f62132 100644 --- a/lib/cart/views/my_postcard_page_view.dart +++ b/lib/cart/views/my_postcard_page_view.dart @@ -1,5 +1,10 @@ +import 'package:citycards_customer/cart/widget/ticket_card_view.dart'; +import 'package:citycards_customer/checkout/widget/all_coupons_bottomsheet.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_bloc/flutter_bloc.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; import '../blocs/postcard_bloc.dart'; class MyPostCardsPage extends StatelessWidget { @@ -12,185 +17,175 @@ class MyPostCardsPage extends StatelessWidget { if (state is PostCardLoading) { return const Center(child: CircularProgressIndicator()); } else if (state is PostCardLoaded) { - final card = state.postcards.first; return SingleChildScrollView( padding: const EdgeInsets.all(16), - child: Column( + child: + Column( children: [ + TicketCard(), + SizedBox(height: 40.h), Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16), - border: Border.all(color: const Color(0xffF1F5F7)), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.1), - blurRadius: 6, - offset: const Offset(0, 3), - ), - ], + padding: EdgeInsets.symmetric( + horizontal: 12.w, + vertical: 12.h, ), - child: Column( + decoration: BoxDecoration( + color: Color(0xFFFFF5F5), + borderRadius: BorderRadius.circular(8.r), + border: Border.all( + color: Color(0xFFBB474A).withOpacity(0.4), + width: 0.8, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - ClipRRect( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - ), - child: Image.network( - card.imageUrl, - height: 180, - width: double.infinity, - fit: BoxFit.cover, - ), - ), - Container( - decoration: const BoxDecoration( - border: Border( - top: BorderSide( - color: Color(0xffE8E8E8), width: 1, style: BorderStyle.solid), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomText( + text: "Get 10% off on your first trip", + color: Color(0xFF262626), + size: 14.sp, ), + SizedBox(height: 7.h), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + GestureDetector( + onTap: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(12.r), + ), + ), + builder: (_) => AllCouponsBottomsheet(), + ); + }, + child: CustomText( + text: "View all coupons", + color: Color(0xFFF95F62), + size: 12, + ), + ), + SizedBox(width: 3.w), + Icon(Icons.arrow_right, color: Color(0xFFF95F62)), + ], + ), + ], + ), + + const Spacer(), + Container( + padding: EdgeInsets.symmetric( + horizontal: 20.w, + vertical: 10.h, ), - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 4), - Text(card.title, - style: const TextStyle( - fontWeight: FontWeight.bold, fontSize: 16)), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text("Postcards:", - style: TextStyle(color: Colors.black54)), - Text("${card.count}", - style: const TextStyle( - fontWeight: FontWeight.w500)), - ], - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text("Date:", - style: TextStyle(color: Colors.black54)), - Text(card.date, - style: const TextStyle( - fontWeight: FontWeight.w500)), - ], - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text("Time:", - style: TextStyle(color: Colors.black54)), - Text(card.time, - style: const TextStyle( - fontWeight: FontWeight.w500)), - ], - ), - ], - ), - ], + decoration: BoxDecoration( + border: Border.all(color: Color(0xFFF95F62)), + borderRadius: BorderRadius.circular(8.r), + ), + child: CustomText( + text: "Apply", + color: Color(0xFFF95F62), + size: 14.sp, ), ), ], ), ), - const SizedBox(height: 20), - _couponBox(), - const SizedBox(height: 20), - _summaryBox(state), - const SizedBox(height: 30), - _checkoutButton(), + + SizedBox(height: 15.h), + + Divider(color: Color(0xFFACACAC), thickness: 1.h), + SizedBox(height: 10.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomText(text: "Subtotal", size: 14.sp), + CustomText( + text: "\$49.50", + size: 14.sp, + weight: FontWeight.w500, + ), + ], + ), + SizedBox(height: 14.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomText(text: "Discount", size: 14.sp), + CustomText( + text: "-7.20%", + size: 14.sp, + weight: FontWeight.w500, + ), + ], + ), + SizedBox(height: 10.h), + Divider(color: Color(0xFFACACAC), thickness: 1.h), + SizedBox(height: 10.h), + + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomText(text: 'Total', size: 14.sp), + SizedBox(height: 4.h), + CustomText( + text: "Including \$2.24 in taxes", + size: 12.sp, + color: Colors.black.withOpacity(0.6), + ), + ], + ), + ), + CustomText( + text: "\$42.60", + size: 24.sp, + weight: FontWeight.w500, + ), + ], + ), + SizedBox(height: 60.h), + CustomFilledButton( + onTap: () {}, + width: double.infinity, + label: "Proceed to Checkout", + ), ], ), ); } - return const SizedBox.shrink(); + return Center( + child: Column( + children: [ + Image.asset("assets/gif/empty_post_card.gif", width: 250.w), + Text( + "You do not have any postcards", + style: TextStyle( + fontSize: 24.sp, + color: Color(0xFFF95F62) + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 4.h), + Text( + "You do not possess any postcards yet nor have you sent to anyone", + style: TextStyle(color: Color(0xFF656565), fontSize: 14.sp), + textAlign: TextAlign.center, + ), + ], + ), + ); }, ); } - - Widget _couponBox() { - return Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: const Color(0xffFFF5F5), - ), - child: Row( - children: [ - const Expanded( - child: Text("Get 10% off on your first trip", - style: TextStyle(fontWeight: FontWeight.w500))), - Container( - padding: - const EdgeInsets.symmetric(horizontal: 20, vertical: 8), - decoration: BoxDecoration( - color: Colors.white, - border: Border.all(color: Colors.redAccent), - borderRadius: BorderRadius.circular(8), - ), - child: const Text("Apply", - style: TextStyle(color: Colors.redAccent)), - ) - ], - ), - ); - } - - Widget _summaryBox(PostCardLoaded state) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _row("Subtotal", "\$${state.subtotal.toStringAsFixed(2)}"), - _row("Discount", "-${state.discountPercent.toStringAsFixed(2)}%"), - const Divider(), - _row("Total", "\$${state.total.toStringAsFixed(2)}", - bold: true, large: true), - const Text("Including \$2.24 in taxes", - style: TextStyle(color: Colors.black54, fontSize: 12)), - ], - ); - } - - Widget _row(String label, String value, - {bool bold = false, bool large = false}) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(label, - style: TextStyle( - fontWeight: bold ? FontWeight.bold : FontWeight.normal, - fontSize: large ? 18 : 14)), - Text(value, - style: TextStyle( - fontWeight: bold ? FontWeight.bold : FontWeight.normal, - fontSize: large ? 18 : 14)), - ], - ), - ); - } - - Widget _checkoutButton() { - return Container( - width: double.infinity, - padding: const EdgeInsets.symmetric(vertical: 14), - decoration: BoxDecoration( - color: Colors.redAccent, - borderRadius: BorderRadius.circular(30), - ), - child: const Center( - child: Text("Proceed to Checkout", - style: TextStyle(color: Colors.white, fontSize: 16)), - ), - ); - } } diff --git a/lib/cart/widget/ticket_card_view.dart b/lib/cart/widget/ticket_card_view.dart new file mode 100644 index 0000000..7ce099f --- /dev/null +++ b/lib/cart/widget/ticket_card_view.dart @@ -0,0 +1,171 @@ +import 'package:citycards_customer/common_packages/custom_dashed_line.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class TicketCard extends StatelessWidget { + const TicketCard({super.key}); + + @override + Widget build(BuildContext context) { + return Center( + child: CustomPaint( + painter: TicketPainter(), + child: ClipPath( + clipper: TicketClipper(), + child: Container( + width: 270.w, + height: 410.h, + padding: EdgeInsets.all(16.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12.r), + ), + child: Column( + children: [ + // Image Section + ClipRRect( + borderRadius: BorderRadius.circular(12.r), + child: Image.asset( + 'assets/images/card_banner.png', + width: double.infinity, + height: 198.h, + fit: BoxFit.cover, + ), + ), + SizedBox(height: 24.h), + + // Dashed divider + SizedBox( + width: 200.w, + child: DashedDivider( + color: const Color(0xFFBEBEBE), + thickness: 2.h, + ), + ), + + Text( + "Melbourne", + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.w500, + ), + ), + SizedBox(height: 12.h), + _infoRow("Postcards :", "5"), + _infoRow("Date :", "22/04/2025"), + _infoRow("Time :", "12:00PM - 2:00PM"), + ], + ), + ), + ), + ), + ); + } + + Widget _infoRow(String title, String value) { + return Padding( + padding: EdgeInsets.symmetric(vertical: 6.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: TextStyle(color: const Color(0xFF808080), fontSize: 12.sp), + ), + Text( + value, + style: TextStyle(fontWeight: FontWeight.w400, fontSize: 12.sp), + ), + ], + ), + ); + } +} + +class TicketPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + const notchRadius = 23.0; + const dividerY = 222.0; + + final ticketPath = Path() + ..moveTo(12, 0) + ..lineTo(size.width - 12, 0) + ..arcToPoint(Offset(size.width, 12), radius: const Radius.circular(12)) + ..lineTo(size.width, dividerY - notchRadius) + ..arcToPoint( + Offset(size.width, dividerY + notchRadius), + radius: const Radius.circular(notchRadius), + clockwise: false, + ) + ..lineTo(size.width, size.height - 12) + ..arcToPoint(Offset(size.width - 12, size.height), + radius: const Radius.circular(12)) + ..lineTo(12, size.height) + ..arcToPoint(Offset(0, size.height - 12), + radius: const Radius.circular(12)) + ..lineTo(0, dividerY + notchRadius) + ..arcToPoint( + Offset(0, dividerY - notchRadius), + radius: const Radius.circular(notchRadius), + clockwise: false, + ) + ..lineTo(0, 12) + ..arcToPoint(Offset(12, 0), radius: const Radius.circular(12)) + ..close(); + + // šŸŒ‘ Draw even soft black shadow around all sides + final shadowPaint = Paint() + ..color = Colors.black.withOpacity(0.3) + ..maskFilter = const MaskFilter.blur(BlurStyle.outer, 8); + + canvas.drawPath(ticketPath, shadowPaint); + + final cardPaint = Paint() + ..color = const Color(0xFFFAC9CA).withOpacity(0.12) + ..style = PaintingStyle.fill; + + canvas.drawPath(ticketPath, cardPaint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} + +class TicketClipper extends CustomClipper { + @override + Path getClip(Size size) { + const notchRadius = 23.0; + const dividerY = 222.0; + + final path = Path() + ..moveTo(12, 0) + ..lineTo(size.width - 12, 0) + ..arcToPoint(Offset(size.width, 12), radius: const Radius.circular(12)) + ..lineTo(size.width, dividerY - notchRadius) + ..arcToPoint( + Offset(size.width, dividerY + notchRadius), + radius: const Radius.circular(notchRadius), + clockwise: false, + ) + ..lineTo(size.width, size.height - 12) + ..arcToPoint(Offset(size.width - 12, size.height), + radius: const Radius.circular(12)) + ..lineTo(12, size.height) + ..arcToPoint(Offset(0, size.height - 12), + radius: const Radius.circular(12)) + ..lineTo(0, dividerY + notchRadius) + ..arcToPoint( + Offset(0, dividerY - notchRadius), + radius: const Radius.circular(notchRadius), + clockwise: false, + ) + ..lineTo(0, 12) + ..arcToPoint(Offset(12, 0), radius: const Radius.circular(12)) + ..close(); + + return path; + } + + @override + bool shouldReclip(covariant CustomClipper oldClipper) => false; +} 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 246ad54..5ce038a 100644 --- a/lib/core/app_router.dart +++ b/lib/core/app_router.dart @@ -14,10 +14,14 @@ 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/offer_pass_detail/offer_pass_detail_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'; @@ -122,46 +126,82 @@ 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(); + }); + + case RouteConstants.offerPassDetail: + return MaterialPageRoute(builder: (_){ + return OfferPassDetailView(); }); 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 1ff1ba5..d9abccd 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 *****************************************/ @@ -37,10 +39,12 @@ class RouteConstants { static const String searchOffer = '/searchOffer'; static const String createAcct = '/createAcct'; static const String addDetails = '/addDetails'; + static const String offerPassDetail = "/offerPassDetail"; /************************** My card page ***************************************/ static const String cartPage = '/cartPage'; + static const String yourItinerary = '/yourItinerary'; static const String qrPage = '/qrPage'; 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..bb6ada6 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/goto_school.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/offer_pass_detail/offer_pass_detail_view.dart b/lib/offer_pass_detail/offer_pass_detail_view.dart new file mode 100644 index 0000000..a963299 --- /dev/null +++ b/lib/offer_pass_detail/offer_pass_detail_view.dart @@ -0,0 +1,252 @@ +import 'package:citycards_customer/attraction_details/share_bottomsheet.dart'; +import 'package:citycards_customer/common_packages/app_bar.dart'; +import 'package:citycards_customer/common_packages/custom_bullet_points.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +class OfferPassDetailView extends StatelessWidget { + const OfferPassDetailView({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + body: SafeArea( + child: SingleChildScrollView( + child: Column( + children: [ + Stack( + children: [ + Image.asset( + 'assets/images/koh_rong_samloem_banner.png', + height: 377.h, + width: double.infinity, + fit: BoxFit.cover, + ), + Positioned( + top: 0, + left: 0, + right: 0, + child: SafeArea( + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: 20.w, + vertical: 10.h, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CommonAppBar( + isWhiteLogo: true, + isProfilePage: false, + ), + + SizedBox(height: 10.h), + Divider( + color: Colors.white.withOpacity(0.6), + height: 1.h, + ), + SizedBox(height: 8.h), + + Row( + children: [ + GestureDetector( + onTap: () => Navigator.pop(context), + child: Icon( + Icons.arrow_back, + size: 24.sp, + color: Colors.white, + ), + ), + SizedBox(width: 8.w), + Text( + "Aster Hotels", + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + ], + ), + ], + ), + ), + ), + ), + + Positioned( + bottom: 31.h, + left: 12.w, + child: Text( + "Aster \nHotels", + style: TextStyle( + color: Colors.white, + fontSize: 48.sp, + fontWeight: FontWeight.w500, + height: 1.2, + ), + ), + ), + + Positioned( + bottom: 31.h, + right: 17.w, + child: GestureDetector( + onTap: () { + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (context) => const ShareBottomSheet(), + ); + }, + child: Container( + height: 36.h, + width: 36.w, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20.r), + ), + child: Center( + child: Icon( + Icons.share_sharp, + color: Colors.black, + size: 18.sp, + ), + ), + ), + ), + ), + ], + ), + Padding( + padding: EdgeInsets.symmetric( + horizontal: 20.0.w, + vertical: 30.5.h, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "About Aster Hotels", + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.w600, + ), + ), + SizedBox(height: 8.h), + Text( + "20% Off on dining and drinks on purchase upto \$500 T&Cs* apply", + style: TextStyle( + fontSize: 14.sp, + color: Colors.black, + fontWeight: FontWeight.w400, + ), + ), + SizedBox(height: 10.h), + Text( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + "Convallis condimentum morbi non egestas enim amet sagittis. " + "Proin sed aliquet rhoncus ut pellentesque ullamcorper sit eget ac. " + "Sit nisi, cras amet varius eget egestas pellentesque. Cursus gravida euismod non...", + style: TextStyle( + fontSize: 14.sp, + height: 1.4, + color: const Color(0xFF656565), + ), + ), + SizedBox(height: 40.h), + + // How to make booking + Text( + "How to make a booking?", + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.w400, + ), + ), + SizedBox(height: 16.h), + + CustomBulletPoints( + text: + "Check the expiration date of your coupon to ensure it's still valid.", + textColor: Color(0xFF656565), + ), + CustomBulletPoints( + text: + "Visit the store or website where the coupon can be redeemed.", + textColor: Color(0xFF656565), + ), + CustomBulletPoints( + text: + "If shopping online, add items to your cart and proceed to checkout.", + textColor: Color(0xFF656565), + ), + CustomBulletPoints( + text: + "Look for a field labeled 'Coupon Code' or 'Promo Code' during checkout.", + textColor: Color(0xFF656565), + ), + CustomBulletPoints( + text: + "Enter your coupon code exactly as it appears, including any special characters.", + textColor: Color(0xFF656565), + ), + SizedBox(height: 24.h), + + // Coupon Box + Container( + width: double.infinity, + height: 48.h, + padding: EdgeInsets.symmetric( + vertical: 12.h, + horizontal: 24.w, + ), + decoration: BoxDecoration( + color: const Color(0xFFFEE7E7), + borderRadius: BorderRadius.circular(10.r), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "AFJIJFJ500", + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w600, + letterSpacing: 0.5, + ), + ), + GestureDetector( + onTap: () { + Clipboard.setData( + const ClipboardData(text: "AFJIJFJ500"), + ); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text("Coupon code copied!"), + duration: Duration(seconds: 1), + ), + ); + }, + child: Icon( + Icons.copy_outlined, + color: Color(0xFF464646), + size: 20.sp, + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + } +} 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