Merge remote-tracking branch 'origin/vinayak' into dinesh

# Conflicts:
#	lib/main.dart
This commit is contained in:
2025-10-29 16:13:43 +05:30
38 changed files with 2439 additions and 460 deletions

View File

@@ -1,4 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application
android:label="citycards_customer"

BIN
assets/gif/empty_cart.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 KiB

BIN
assets/gif/goto_school.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 MiB

View File

@@ -8,7 +8,6 @@ import '../blocs/postcard_bloc.dart';
import 'my_pass_page_view.dart';
import 'my_postcard_page_view.dart';
class MyCartPage extends StatefulWidget {
const MyCartPage({super.key});
@@ -35,11 +34,13 @@ class _MyCartPageState extends State<MyCartPage> {
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<MyCartPage> {
child: selectedTab == 0
? const MyPassesPage()
: const MyPostCardsPage(),
)
),
],
),
],

View File

@@ -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)),
),
);
}
}

View File

@@ -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)),
),
);
}
}

View File

@@ -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<Path> {
@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<Path> oldClipper) => false;
}

View File

@@ -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
),
),
],

View File

@@ -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),
),
],
);
}
}

View File

@@ -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'))),
);
}
}

View File

@@ -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';

View File

@@ -68,7 +68,7 @@ class ItineraryDetailState {
final String? selectedCity;
final String? selectedEnergy;
final String? withKid;
final List<String>? 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<String>? 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<AddDietaryToItinerary>((event, emit) {
final currentSelection = List<String>.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<String>.from(state.selectedDietary ?? []);
//
// if (currentSelection.contains(event.dietary)) {
// currentSelection.remove(event.dietary);
// } else {
// currentSelection.add(event.dietary);
// }
emit(state.copyWith(selectedDietary: event.dietary));
});
on<AddMuseumRating>((event, emit) {

View File

@@ -22,7 +22,7 @@ class ItineraryStepNavigationBloc
on<ItineraryStepNavigationNextEvent>((event, emit) {
final nextIndex = state.selectedIndex + 1;
if (nextIndex <= 10) {
if (nextIndex <= 11) {
emit(ItineraryStepNavigationState(nextIndex));
}
});

View File

@@ -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: [

View File

@@ -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(

View File

@@ -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<CurrentLocationSelection> createState() =>
_CurrentLocationSelectionState();
}
class _CurrentLocationSelectionState extends State<CurrentLocationSelection> {
final TextEditingController _controller = TextEditingController();
LatLng? _currentLatLng;
Future<void> _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<ItineraryStepNavigationBloc>().add(
ItineraryStepNavigationNextEvent(),
);
},
label: "Continue",
showArrow: true,
),
SizedBox(height: 20.h),
],
),
),
),
);
}
}

View File

@@ -51,62 +51,56 @@ class _DietarySelectionViewState extends State<DietarySelectionView> {
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<AddItineraryDetailBloc>().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<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 == item['name'];
return GestureDetector(
onTap: () {
context.read<AddItineraryDetailBloc>().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),

View File

@@ -79,7 +79,7 @@ class ItineraryCompletionView extends StatelessWidget {
),
_buildProfileRow(
"Dietary",
(state.selectedDietary ?? []).join(', '),
state.selectedDietary ?? "",
),
_buildProfileRow(
"Museums",

View File

@@ -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(

View File

@@ -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(

View File

@@ -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<ItineraryCreationPage> {
>(
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<ItineraryCreationPage> {
>(
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<ItineraryCreationPage> {
physics: const NeverScrollableScrollPhysics(),
children: [
DateSelectionView(),
CurrentLocationSelection(),
CitySelectionView(),
EnergySelectionView(),
KidsSelectionView(),

View File

@@ -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 Dont 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,)
],
),
),
),
);
}
}

View File

@@ -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,
),
),
),
],
),
);
}
}

View File

@@ -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,
),
),
],
),
),
],
),
),
],
),
),
),
);
}
}

View File

@@ -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;
}

View File

@@ -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<ChangeItineraryDayTabEvent, ItineraryDayTabState> {
ItineraryChangeDayTabBloc() : super(ItineraryDayTabState(0)) {
on<ChangeItineraryDayTabEvent>((event, emit) {
emit(ItineraryDayTabState(event.tabIndex));
});
}
}

View File

@@ -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<ChangeItineraryTabEvent, ItineraryTabState> {
ItineraryChangeTabBloc() : super(ItineraryTabState(0)) {
on<ChangeItineraryTabEvent>((event, emit) {
emit(ItineraryTabState(event.tabIndex));
});
}
}

View File

@@ -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<ItineraryChangeTabBloc, ItineraryTabState>(
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 Pellegrinis 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<ItineraryChangeDayTabBloc, ItineraryDayTabState>(
builder: (context, state) {
final isActive = state.tabIndex == index;
return GestureDetector(
onTap: () {
context.read<ItineraryChangeDayTabBloc>().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),
),
),
),
),
);
},
);
}
}

View File

@@ -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<String> amenities;
final List<String> 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],
);
}),
],
),
);
}
}

View File

@@ -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<ItineraryChangeTabBloc, ItineraryTabState>(
builder: (context, state) {
final isActive = state.tabIndex == index;
return GestureDetector(
onTap: () {
context
.read<ItineraryChangeTabBloc>()
.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
),
),
),
),
);
},
);
}
}

View File

@@ -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<Map<String, dynamic>> itineraryStops = [
{
"title": "9:00 am: Pallegrini Expresso Bar",
"details": [
"Coffee at Pellegrinis 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 Pellegrinis 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 Pellegrinis 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,
),
],
),
),
],
),
],
),
);
}),
],
),
);
}
}

View File

@@ -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:

View File

@@ -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