working on my pass section

This commit is contained in:
2025-10-27 15:44:13 +05:30
parent 4e7e2cad0e
commit 7c146c9f1e
14 changed files with 911 additions and 11 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 KiB

View File

@@ -0,0 +1,40 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import '../model/pass_model.dart';
abstract class PassEvent {}
class LoadPasses extends PassEvent {}
abstract class PassState {}
class PassLoading extends PassState {}
class PassLoaded extends PassState {
final List<PassModel> passes;
final double subtotal;
final double discountPercent;
final double total;
PassLoaded(this.passes, this.subtotal, this.discountPercent, this.total);
}
class PassBloc extends Bloc<PassEvent, PassState> {
PassBloc() : super(PassLoading()) {
on<LoadPasses>((event, emit) {
final passes = [
PassModel(
title: "Melbourne",
imageUrl: "assets/images/city_melbourne.png",
duration: "2 days",
adults: 3,
kids: 3,
quantity: 2,
price: 49.50,
discount: 7.2,
),
];
final subtotal = passes.fold(0.0, (sum, item) => sum + item.price);
final discountPercent = passes.first.discount;
final total = subtotal - (subtotal * discountPercent / 100);
emit(PassLoaded(passes, subtotal, discountPercent, total));
});
}
}

View File

@@ -0,0 +1,40 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import '../model/postcard_model.dart';
abstract class PostCardEvent {}
class LoadPostCards extends PostCardEvent {}
abstract class PostCardState {}
class PostCardLoading extends PostCardState {}
class PostCardLoaded extends PostCardState {
final List<PostCardModel> postcards;
final double subtotal;
final double discountPercent;
final double total;
PostCardLoaded(this.postcards, this.subtotal, this.discountPercent, this.total);
}
class PostCardBloc extends Bloc<PostCardEvent, PostCardState> {
PostCardBloc() : super(PostCardLoading()) {
on<LoadPostCards>((event, emit) {
final postcards = [
PostCardModel(
title: "Melbourne",
imageUrl: "https://cdn.pixabay.com/photo/2016/11/22/19/31/melbourne-1853216_1280.jpg",
count: 5,
date: "22/04/2025",
time: "12:00PM - 2:00PM",
price: 49.50,
discount: 7.2,
),
];
final subtotal = postcards.fold(0.0, (sum, item) => sum + item.price);
final discountPercent = postcards.first.discount;
final total = subtotal - (subtotal * discountPercent / 100);
emit(PostCardLoaded(postcards, subtotal, discountPercent, total));
});
}
}

View File

@@ -0,0 +1,21 @@
class PassModel {
final String title;
final String imageUrl;
final String duration;
final int adults;
final int kids;
final int quantity;
final double price;
final double discount;
PassModel({
required this.title,
required this.imageUrl,
required this.duration,
required this.adults,
required this.kids,
required this.quantity,
required this.price,
required this.discount,
});
}

View File

@@ -0,0 +1,19 @@
class PostCardModel {
final String title;
final String imageUrl;
final int count;
final String date;
final String time;
final double price;
final double discount;
PostCardModel({
required this.title,
required this.imageUrl,
required this.count,
required this.date,
required this.time,
required this.price,
required this.discount,
});
}

View File

@@ -0,0 +1,98 @@
import 'package:citycards_customer/common_packages/app_bar.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../common_packages/back_widget.dart';
import '../blocs/pass_bloc.dart';
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});
@override
State<MyCartPage> createState() => _MyCartPageState();
}
class _MyCartPageState extends State<MyCartPage> {
int selectedTab = 0;
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(create: (_) => PassBloc()..add(LoadPasses())),
BlocProvider(create: (_) => PostCardBloc()..add(LoadPostCards())),
],
child: Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: SingleChildScrollView(
padding: EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
CommonAppBar(isWhiteLogo: false, isProfilePage: false, showCart: false,),
backWidget(context, "Your Cart"),
SizedBox(
height: 24.h,
),
Container(
padding: EdgeInsets.all(4.0),
margin: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xffFEE7E7),
borderRadius: BorderRadius.circular(30),
),
child: Row(
children: [
_tabButton("My Passes", 0),
_tabButton("My Post Cards", 1),
],
),
),
Row(
children: [
Expanded(
child: selectedTab == 0
? const MyPassesPage()
: const MyPostCardsPage(),
)
],
),
],
),
),
),
),
);
}
Expanded _tabButton(String title, int index) {
final bool isSelected = selectedTab == index;
return Expanded(
child: GestureDetector(
onTap: () => setState(() => selectedTab = index),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color: isSelected ? Colors.white : Colors.transparent,
borderRadius: BorderRadius.circular(30),
),
child: Center(
child: Text(
title,
style: TextStyle(
fontWeight: FontWeight.w400,
color: Color(0xff2A2A2A),
),
),
),
),
),
);
}
}

View File

@@ -0,0 +1,173 @@
import 'package:citycards_customer/cart/views/view_pass_page_view.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../blocs/pass_bloc.dart';
class MyPassesPage extends StatelessWidget {
const MyPassesPage({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<PassBloc, PassState>(
builder: (context, state) {
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()),
),
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,
),
),
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(
children: [
const Icon(Icons.person, size: 10,),
Text(" ${pass.adults} adults "),
Text("Qty: ${pass.quantity}"),
],
),
],
),
),
),
],
),
),
),
const SizedBox(height: 20),
_couponBox(),
const SizedBox(height: 20),
_summaryBox(state),
const SizedBox(height: 30),
_checkoutButton(),
],
),
);
}
return const SizedBox.shrink();
},
);
}
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

@@ -0,0 +1,196 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../blocs/postcard_bloc.dart';
class MyPostCardsPage extends StatelessWidget {
const MyPostCardsPage({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<PostCardBloc, PostCardState>(
builder: (context, state) {
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(
children: [
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),
),
],
),
child: Column(
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),
),
),
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)),
],
),
],
),
],
),
),
],
),
),
const SizedBox(height: 20),
_couponBox(),
const SizedBox(height: 20),
_summaryBox(state),
const SizedBox(height: 30),
_checkoutButton(),
],
),
);
}
return const SizedBox.shrink();
},
);
}
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,275 @@
import 'package:flutter/material.dart';
class ViewPassPage extends StatelessWidget {
const ViewPassPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.white,
title: const Text("View Pass", style: TextStyle(color: Colors.black)),
leading: const BackButton(color: Colors.black),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_passCard(),
const SizedBox(height: 20),
const Text("Features",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
const SizedBox(height: 10),
_featuresBox(),
const SizedBox(height: 20),
const Text("Available Attractions",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
const SizedBox(height: 10),
_attractionList(),
const SizedBox(height: 10),
_viewAllButton(),
const SizedBox(height: 20),
const Text("Card Benefits",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
const SizedBox(height: 10),
_benefitsRow(),
],
),
),
);
}
Widget _passCard() {
return Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: const Color(0xffF1F5F7)),
borderRadius: BorderRadius.circular(16),
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.network(
"https://cdn.pixabay.com/photo/2016/11/22/19/31/melbourne-1853216_1280.jpg",
width: 100,
height: 100,
fit: BoxFit.cover,
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text("Melbourne",
style:
TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
SizedBox(height: 4),
Text("2 days"),
Row(
children: [
Icon(Icons.person, size: 16),
Text(" 3 adults "),
Icon(Icons.child_care, size: 16),
Text("3 kids "),
Text("Qty: 2"),
],
),
],
),
),
),
Container(
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: const BoxDecoration(
color: Color(0xffFFEBEE),
borderRadius: BorderRadius.only(
topRight: Radius.circular(16),
bottomRight: Radius.circular(16),
),
),
child: const Text(
"\$49.50",
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold,
fontSize: 16),
),
),
],
),
);
}
Widget _featuresBox() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xffFFF5F5),
borderRadius: BorderRadius.circular(12),
),
child: const Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("• Pass category"),
Text("• Pricing"),
Text("• Access to attractions"),
Text("• Entry to attractions"),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("• Pass category"),
Text("• Pricing"),
Text("• Access to attractions"),
Text("• Entry to attractions"),
],
),
],
),
);
}
Widget _attractionList() {
final List<String> attractions = [
"https://cdn.pixabay.com/photo/2020/04/14/20/41/singapore-5041036_1280.jpg",
"https://cdn.pixabay.com/photo/2023/02/23/15/54/cityscape-7809647_1280.jpg",
"https://cdn.pixabay.com/photo/2019/12/13/14/29/dubai-4693906_1280.jpg",
"https://cdn.pixabay.com/photo/2016/06/29/09/21/singapore-1484302_1280.jpg",
];
return SizedBox(
height: 130,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: attractions.length,
itemBuilder: (context, index) {
return Container(
width: 100,
margin: const EdgeInsets.only(right: 10),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.network(attractions[index], fit: BoxFit.cover),
),
);
},
),
);
}
Widget _viewAllButton() {
return const Align(
alignment: Alignment.center,
child: Text("View all",
style: TextStyle(color: Colors.redAccent, fontSize: 14)),
);
}
Widget _benefitsRow() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_benefitCard(
color: const Color(0xff4C40ED),
title: "50% OFF on eSIM Data Plans",
label: "SPECIAL OFFER",
tag: "NEW",
button: "Get Offer",
),
_benefitCard(
color: const Color(0xffFF736C),
title: "60% OFF Hotel Bookings",
label: "EXCLUSIVE DEAL",
button: "Book Now",
),
],
);
}
Widget _benefitCard({
required Color color,
required String title,
required String label,
required String button,
String? tag,
}) {
return Expanded(
child: Container(
margin: const EdgeInsets.only(right: 8),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(16),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(label,
style: const TextStyle(
color: Colors.white, fontWeight: FontWeight.bold)),
if (tag != null) ...[
const SizedBox(width: 6),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: Colors.yellow,
borderRadius: BorderRadius.circular(8),
),
child: Text(tag,
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 10)),
)
],
],
),
const SizedBox(height: 8),
Text(title,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 14)),
const SizedBox(height: 12),
Container(
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(30),
),
child: Center(
child: Text(button,
style: TextStyle(
color: color,
fontWeight: FontWeight.bold,
fontSize: 12)),
),
),
],
),
),
);
}
}

View File

@@ -31,15 +31,23 @@ class CommonAppBar extends StatelessWidget {
Row(
children: [
if(showCart!)
Container(
padding: const EdgeInsets.all(10),
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
child: Image.asset(
"assets/icons/shopping_cart.png",
height: 20.h,
InkWell(
onTap: (){
Navigator.of(
context,
rootNavigator: true,
).pushNamed(RouteConstants.cartPage);
},
child: Container(
padding: const EdgeInsets.all(10),
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
child: Image.asset(
"assets/icons/shopping_cart.png",
height: 20.h,
),
),
),
SizedBox(width: 8.w),

View File

@@ -0,0 +1,23 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
Widget backWidget(BuildContext context, String title){
return Row(
children: [
GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: Icon(Icons.arrow_back, size: 24.sp),
),
SizedBox(width: 8.w),
Text(
title,
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.w500,
),
),
],
);
}

View File

@@ -1,6 +1,7 @@
import 'package:citycards_customer/Profile/profile_page_view.dart';
import 'package:citycards_customer/attraction_details/attraction_details_view.dart';
import 'package:citycards_customer/buy_a_pass/view/buy_pass_view.dart';
import 'package:citycards_customer/cart/views/my_cart_view_page.dart';
import 'package:citycards_customer/checkout/checkout_view.dart';
import 'package:citycards_customer/common_bloc/language_selection_bloc.dart';
import 'package:citycards_customer/contact_us/contact_us_view.dart';
@@ -131,6 +132,11 @@ class AppRouter {
return MaterialPageRoute(builder: (_){
return CheckoutView();
});
case RouteConstants.cartPage:
return MaterialPageRoute(builder: (_){
return MyCartPage();
});
default:
return MaterialPageRoute(
builder: (_) =>

View File

@@ -35,6 +35,7 @@ class RouteConstants {
static const String buyPass ='/buyPass';
static const String checkout ='/checkout';
/************************** My card page ***************************************/
static const String cartPage = '/cartPage';
}

View File

@@ -69,7 +69,7 @@ class _RegisteredUserHomePageState extends State<RegisteredUserHomePage> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CommonAppBar(isWhiteLogo: true , isProfilePage: false),
SizedBox(height: 30.h),
SizedBox(height: 50.h),
Text(
"Chicago",
style: TextStyle(