8 Commits

Author SHA1 Message Date
Vinayakkadge04
93749af445 Taken pull from origin/dinesh 2025-10-28 11:19:23 +05:30
Vinayakkadge04
c28d6a96af Merge remote-tracking branch 'origin/dinesh' into vinayak
# Conflicts:
#	lib/core/route_constants.dart
#	lib/main.dart
2025-10-28 11:18:07 +05:30
489a42d68c Itineary navigation 2025-10-27 19:45:47 +05:30
66f9abe4e0 Merge remote-tracking branch 'origin/vinayak' into dinesh
# Conflicts:
#	lib/core/app_router.dart
#	lib/main.dart
2025-10-27 15:47:54 +05:30
7c146c9f1e working on my pass section 2025-10-27 15:44:13 +05:30
4e7e2cad0e Merged vinayak's code. 2025-10-27 11:53:27 +05:30
a1d81ea31c Merge remote-tracking branch 'origin/vinayak' into dinesh 2025-10-27 11:46:07 +05:30
9042cd57d5 Completed the postcard section. 2025-10-26 19:34:41 +05:30
34 changed files with 2189 additions and 103 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
assets/icons/edit_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/icons/send_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 882 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@@ -29,8 +29,6 @@ class AttractionsPage extends StatelessWidget {
children: [
// App bar
CommonAppBar(isWhiteLogo: false, isProfilePage: false),
SizedBox(height: 12.h),
Divider(height: 1.h, color: const Color(0xFFD9D9D9)),
SizedBox(height: 22.h),
// Back row

View File

@@ -1,5 +1,4 @@
import 'package:citycards_customer/buy_a_pass/widget/feature_table.dart';
import 'package:citycards_customer/buy_a_pass/widget/offer_card_view.dart';
import 'package:citycards_customer/buy_a_pass/widget/pass_card_view.dart';
import 'package:citycards_customer/buy_a_pass/widget/payment_card_view.dart';
import 'package:citycards_customer/common_packages/app_bar.dart';

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

@@ -21,6 +21,7 @@ import 'package:citycards_customer/terms_and_condition/terms_and_condition_view.
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../attractions/views/attractions_page_view.dart';
import '../cart/views/my_cart_view_page.dart';
import '../common_bloc/bottom_navigation_bloc.dart';
import '../home/views/home_page_view.dart';
import 'route_constants.dart';
@@ -134,6 +135,11 @@ class AppRouter {
return CheckoutView();
});
case RouteConstants.cartPage:
return MaterialPageRoute(builder: (_){
return MyCartPage();
});
case RouteConstants.searchOffer:
return MaterialPageRoute(builder: (_){
return BlocProvider(

View File

@@ -39,4 +39,7 @@ class RouteConstants {
static const String addDetails = '/addDetails';
/************************** My card page ***************************************/
static const String cartPage = '/cartPage';
}

View File

@@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import '../../common_bloc/bottom_navigation_bloc.dart';
import '../../common_packages/custom_bottom_navbar.dart';
import '../../core/inside_bottom_navigator.dart';
import '../../itinerary_creation/views/itinerary_creation_start_view.dart';
import '../../postcard/views/postcard_initial_page_view.dart';
import 'first_time_user_home_page.dart';
@@ -34,7 +35,7 @@ class _HomePageState extends State<HomePage> {
body: Stack(
children: [
buildOffstageNavigator(0, currentIndex, const FirstTimeUserHomePage(), _navigatorKeys[0]),
buildOffstageNavigator(1, currentIndex, const RegisteredUserHomePage(), _navigatorKeys[1]),
buildOffstageNavigator(1, currentIndex, const ItineraryCreationStartPage(), _navigatorKeys[1]),
buildOffstageNavigator(3, currentIndex, const PostcardPage(), _navigatorKeys[3]),
],
),

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(

View File

@@ -15,7 +15,7 @@ class ItineraryStepNavigationState {
class ItineraryStepNavigationBloc
extends Bloc<ItineraryStepNavigationEvent, ItineraryStepNavigationState> {
final int maxIndex; // maximum index allowed
final int maxIndex;
ItineraryStepNavigationBloc({this.maxIndex = 2})
: super(const ItineraryStepNavigationState(0)) {

View File

@@ -72,7 +72,11 @@ class ItineraryCreationStartPage extends StatelessWidget {
SizedBox(height: 47.h),
CustomFilledButton(
onTap: () {
Navigator.pushNamed(context, RouteConstants.itineraryCreation);
Navigator.of(
context,
rootNavigator: true,
).pushNamed(RouteConstants.itineraryCreation);
// Navigator.pushNamed(context, RouteConstants.itineraryCreation);
},
showArrow: true,
label: "Lets Get Started",

View File

@@ -6,7 +6,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:image_picker/image_picker.dart';
import 'package:image/image.dart' as img;
enum PostcardStep { uploadPhoto, addFilter, writeMessage, preview }
enum PostcardStep { uploadPhoto, addFilter, writeMessage, preview, purchase, checkout, orderSuccess, myOrders, myOrderPostcardPreview}
class PostcardCreationBloc
extends Bloc<PostcardCreationEvent, PostcardCreationState> {

View File

@@ -0,0 +1,403 @@
import 'dart:io';
import 'package:citycards_customer/common_packages/app_bar.dart';
import 'package:citycards_customer/postcard/blocs/postcard_creation_events.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:google_fonts/google_fonts.dart';
import '../blocs/postcard_creation_bloc.dart';
import '../blocs/postcard_creation_state.dart';
class MyOrdersPageView extends StatefulWidget {
const MyOrdersPageView({super.key});
@override
State<MyOrdersPageView> createState() => _MyOrdersPageViewState();
}
class _MyOrdersPageViewState extends State<MyOrdersPageView> {
bool showDrafts = true;
@override
Widget build(BuildContext context) {
return BlocBuilder<PostcardCreationBloc, PostcardCreationState>(
builder: (context, state) {
final bloc = context.read<PostcardCreationBloc>();
return SafeArea(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 🏙️ Header
CommonAppBar(isWhiteLogo: false, isProfilePage: false),
Row(
children: [
Expanded(
child: GestureDetector(
onTap: () => setState(() => showDrafts = true),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color: showDrafts
? const Color(0xffF95F62).withOpacity(0.24)
: Colors.transparent,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: showDrafts
? const Color(0xffF95F62).withOpacity(0.4)
: const Color(0xffE0E0E0),
),
),
child: Center(
child: Text(
"My drafts",
style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: 14.sp,
color: showDrafts
? Colors.black
: Colors.black.withOpacity(0.56),
),
),
),
),
),
),
const SizedBox(width: 12),
Expanded(
child: GestureDetector(
onTap: () => setState(() => showDrafts = false),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12),
decoration: BoxDecoration(
color: !showDrafts
? const Color(0xffF95F62).withOpacity(0.24)
: Colors.transparent,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: !showDrafts
? const Color(0xffF95F62).withOpacity(0.4)
: const Color(0xffE0E0E0),
),
),
child: Center(
child: Text(
"My orders",
style: TextStyle(
fontWeight: FontWeight.w400,
fontSize: 14.sp,
color: !showDrafts
? Colors.black
: Colors.black.withOpacity(0.56),
),
),
),
),
),
),
],
),
const SizedBox(height: 24),
// 📬 Postcard List
showDrafts
? Expanded(
child: ListView.builder(
itemCount: 5,
itemBuilder: (context, index) {
return Container(
margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.fromLTRB(
10,
10,
10,
10,
),
decoration: BoxDecoration(
color: const Color(0xFFFFF5F5),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: const Color(0xffF1F5F7),
),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.file(
File(state.imagePath ?? ""),
height: 90.h,
width: 90.w,
fit: BoxFit.cover,
),
),
const SizedBox(width: 20),
Expanded(
child: SizedBox(
height: 90.h,
child: Stack(
children: [
/// Centered texts
Align(
alignment: Alignment.centerLeft,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
"#688574",
style: GoogleFonts.poppins(
fontSize: 14.sp,
fontWeight: FontWeight.w400,
color: Colors.black,
),
),
const SizedBox(height: 3),
Text(
"My postcard",
style: GoogleFonts.poppins(
fontSize: 16.sp,
fontWeight: FontWeight.w400,
color: Colors.black,
),
),
],
),
),
/// 🧭 Bottom-right icons
Align(
alignment: Alignment.bottomRight,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
InkWell(
onTap: () {},
child: Image.asset(
"assets/icons/delete_icon.png",
scale: 4,
),
),
const SizedBox(width: 20),
InkWell(
onTap: () {},
child: Image.asset(
"assets/icons/edit_icon.png",
scale: 4,
),
),
const SizedBox(width: 20),
InkWell(
onTap: () {},
child: Image.asset(
"assets/icons/send_icon.png",
scale: 4,
),
),
const SizedBox(width: 10),
],
),
),
],
),
),
),
],
),
);
},
),
)
: Expanded(
child: ListView.builder(
itemCount: 2,
itemBuilder: (context, index) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"#688574",
style: GoogleFonts.poppins(
fontSize: 14.sp,
fontWeight: FontWeight.w400,
color: Colors.black,
),
),
const SizedBox(height: 3),
Container(
margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.fromLTRB(
10,
10,
10,
10,
),
decoration: BoxDecoration(
color: const Color(0xFFFFF5F5),
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: const Color(0xffF1F5F7),
),
),
child: Row(
crossAxisAlignment:
CrossAxisAlignment.center,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.file(
File(state.imagePath ?? ""),
height: 70.h,
width: 70.w,
fit: BoxFit.cover,
),
),
const SizedBox(width: 20),
Expanded(
child: SizedBox(
height: 60.h,
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
mainAxisAlignment:
MainAxisAlignment.start,
children: [
Text(
"My PostCard",
style: GoogleFonts.poppins(
color: Colors.black,
fontWeight:
FontWeight.w400,
fontSize: 16.sp,
),
),
const SizedBox(height: 6),
Text(
"5 Post cards",
style: GoogleFonts.poppins(
color: Colors.black,
fontWeight:
FontWeight.w400,
fontSize: 14.sp,
),
),
],
),
Column(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
crossAxisAlignment:
CrossAxisAlignment.end,
children: [
Container(
padding: EdgeInsets.fromLTRB(13, 7, 13, 7),
decoration: BoxDecoration(
color: Color(
0xff00FFA6,
).withOpacity(0.16),
border: Border.all(
color: Color(
0xff439F6E,
),
),
borderRadius:
BorderRadius.circular(
16,
),
),
child: Text(
"In Progress",
style: TextStyle(
color: Colors.black,
fontWeight:
FontWeight.w400,
fontSize: 8.54.sp,
),
),
),
InkWell(
onTap: () {
bloc.add(GoToNextStep());
},
child: Row(
children: [
Icon(
Icons
.remove_red_eye_outlined,
size: 15,
color: Color(
0xffF95F62,
),
),
SizedBox(width: 5.w),
Text(
"Preview",
style: TextStyle(
fontWeight:
FontWeight.w400,
color: Color(
0xffF95F62,
),
),
),
],
),
),
],
),
],
),
),
),
],
),
),
],
);
},
),
),
// Create postcard button
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xffF95F62),
padding: EdgeInsets.symmetric(vertical: 16.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(40),
),
),
child: Text(
"Create post card",
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: 14.sp,
fontWeight: FontWeight.w600,
),
),
),
),
],
),
),
);
},
);
}
}

View File

@@ -0,0 +1,164 @@
import 'package:citycards_customer/postcard/blocs/postcard_creation_events.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../common_packages/app_bar.dart';
import '../blocs/postcard_creation_bloc.dart';
import '../blocs/postcard_creation_state.dart';
import '../widgets/postcard_preview_widget.dart';
class OrderPostcardPreviewPageView extends StatefulWidget {
const OrderPostcardPreviewPageView({super.key});
@override
State<OrderPostcardPreviewPageView> createState() => _OrderPostcardPreviewPageViewState();
}
class _OrderPostcardPreviewPageViewState extends State<OrderPostcardPreviewPageView> {
bool showImage = true;
@override
Widget build(BuildContext context) {
return BlocBuilder<PostcardCreationBloc, PostcardCreationState>(
builder: (context, state) {
final bloc = context.read<PostcardCreationBloc>();
return SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CommonAppBar(isWhiteLogo: false, isProfilePage: false),
Row(
children: [
GestureDetector(
onTap: () {
bloc.add(GoToPreviousStep());
},
child: Icon(Icons.arrow_back, size: 24.sp),
),
SizedBox(width: 8.w),
Text(
"Preview",
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w400,
color: Colors.black87,
),
),
],
),
SizedBox(height: 25.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("#688574", style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w400,
fontSize: 14.sp
)),
Row(
mainAxisSize: MainAxisSize.min,
children: [
InkWell(
onTap: () {},
child: Image.asset(
"assets/icons/delete_icon.png",
scale: 3.5,
),
),
const SizedBox(width: 20),
InkWell(
onTap: () {},
child: Image.asset(
"assets/icons/edit_icon.png",
scale: 3.5,
),
),
const SizedBox(width: 20),
InkWell(
onTap: () {},
child: Image.asset(
"assets/icons/send_icon.png",
scale: 3.5,
),
),
const SizedBox(width: 10),
],
)
],
),
SizedBox(height: 50.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton.icon(
onPressed: showImage
? () => setState(() => showImage = false) : null,
icon: Icon(Icons.arrow_back,
color: showImage
? const Color(0xffF95F62)
: const Color(0xffC8C8C8), size: 18),
label: Text(
"Flip",
style: TextStyle(
color: showImage
? const Color(0xffF95F62)
: const Color(0xffC8C8C8),
fontWeight: FontWeight.w500,
),
),
),
TextButton.icon(
onPressed: showImage
? null
: () => setState(() => showImage = true),
icon: Text(
"Flip",
style: TextStyle(
color: !showImage
? const Color(0xffF95F62)
: const Color(0xffC8C8C8),
fontWeight: FontWeight.w500,
),
),
label: Icon(Icons.arrow_forward,
color: !showImage
? const Color(0xffF95F62)
: const Color(0xffC8C8C8),size: 18),
),
],
),
showImage ?
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.asset(
"assets/images/post_card_intro.png",
width: double.infinity,
fit: BoxFit.cover,
),
):
PostCardPreviewWidget(
imagePath: state.imagePath ?? "",
message: state.message ?? "",
selectedFont: state.selectedFont,
),
const SizedBox(height: 24),
],
),
),
);
},
);
}
}

View File

@@ -0,0 +1,125 @@
import 'dart:io';
import 'package:citycards_customer/postcard/blocs/postcard_creation_events.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../common_packages/app_bar.dart';
import '../blocs/postcard_creation_bloc.dart';
import '../blocs/postcard_creation_state.dart';
import '../widgets/postcard_preview_widget.dart';
class OrderSuccessPageView extends StatelessWidget {
const OrderSuccessPageView({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<PostcardCreationBloc, PostcardCreationState>(
builder: (context, state) {
final bloc = context.read<PostcardCreationBloc>();
return SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CommonAppBar(isWhiteLogo: false, isProfilePage: false),
Text(
"🎉🥳",
style: TextStyle(fontSize: 40.sp),
textAlign: TextAlign.center,
),
const SizedBox(height: 20),
Text(
"Order placed successful!",
style: TextStyle(
fontSize: 20.sp,
fontWeight: FontWeight.w500,
color: const Color(0xff1A1A1A),
),
),
const SizedBox(height: 8),
RichText(
textAlign: TextAlign.center,
text: TextSpan(
style: GoogleFonts.poppins(
fontSize: 14.sp,
fontWeight: FontWeight.w400,
color: const Color(0xff585858),
),
children: const [
TextSpan(text: "Your order has been placed. Your order\nid is "),
TextSpan(
text: "#AG74563",
style: TextStyle(
fontWeight: FontWeight.w600,
color: Color(0xff585858),
),
),
],
),
),
const SizedBox(height: 10),
Text(
"It will be delivered in 23 business \ndays.",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 13.sp,
color: const Color(0xff585858),
),
),
const SizedBox(height: 28),
Container(
padding: EdgeInsets.fromLTRB(30, 10, 30, 10),
child: Transform.rotate(
angle: 0.08,
child: PostCardPreviewWidget(
imagePath: state.imagePath ?? "",
message: state.message ?? "",
selectedFont: state.selectedFont,
),
),
),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
bloc.add(GoToNextStep());
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xffF95F62),
padding: EdgeInsets.symmetric(vertical: 16.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(40),
),
),
child: Text(
"Go to My Orders",
style: TextStyle(
color: Colors.white,
fontSize: 14.sp,
fontWeight: FontWeight.w600,
),
),
),
),
],
),
),
);
},
);
}
}

View File

@@ -0,0 +1,194 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../common_packages/app_bar.dart';
import '../blocs/postcard_creation_bloc.dart';
import '../blocs/postcard_creation_events.dart';
import '../blocs/postcard_creation_state.dart';
import '../widgets/postcard_preview_widget.dart';
class PostcardCheckoutPageView extends StatelessWidget {
const PostcardCheckoutPageView({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<PostcardCreationBloc, PostcardCreationState>(
builder: (context, state) {
final bloc = context.read<PostcardCreationBloc>();
return SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CommonAppBar(isWhiteLogo: false, isProfilePage: false),
// Header
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Checkout",
style: GoogleFonts.poppins(
fontSize: 20.sp,
fontWeight: FontWeight.w600,
color: const Color(0xff1A1A1A),
),
),
TextButton(
onPressed: () {
// TODO: Save as draft
},
child: Text(
"Save as draft",
style: GoogleFonts.poppins(
fontSize: 14.sp,
fontWeight: FontWeight.w500,
color: const Color(0xffF95F62),
),
),
),
],
),
const SizedBox(height: 16),
PostCardPreviewWidget(
imagePath: state.imagePath ?? "",
message: state.message ?? "",
selectedFont: state.selectedFont,
),
SizedBox(height: 60.h),
// 💰 Payment Summary
Text(
"Payment summary",
style: TextStyle(
fontSize: 12.sp,
fontWeight: FontWeight.w400,
color: const Color(0xff999999),
),
),
Divider(color: Color(0xffEDEDED)),
const SizedBox(height: 5),
_buildPaymentRow("Subtotal", "\$ 50"),
const SizedBox(height: 20),
_buildPaymentRow(
"Discount",
"\$ 20",
highlight: true,
),
const SizedBox(height: 8),
Divider(color: Colors.black),
_buildPaymentRow("Grand Total", "\$ 30", size: 20.sp),
const SizedBox(height: 28),
Container(
color: Color(0xffFAFAFA),
height: 10,
),
const SizedBox(height: 10),
Row(
children: [
const Icon(Icons.home_outlined,
color: Color(0xffF95F62), size: 20),
const SizedBox(width: 10),
Expanded(
child: Text(
"Unit 7, Level 3, Dummy Towers 33.......",
style: GoogleFonts.poppins(
fontSize: 13.sp,
color: const Color(0xff2D3134),
),
overflow: TextOverflow.ellipsis,
),
),
IconButton(
onPressed: () {
},
icon: const Icon(Icons.edit_outlined,
color: Color(0xffF95F62), size: 18),
),
],
),
const SizedBox(height: 10),
Container(
color: Color(0xffFAFAFA),
height: 10,
),
const SizedBox(height: 40),
// 🧾 Pay Button
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
bloc.add(GoToNextStep());
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xffF95F62),
padding: EdgeInsets.symmetric(vertical: 16.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(40),
),
),
child: Text(
"Pay \$30",
style: TextStyle(
color: Colors.white,
fontSize: 14.sp,
fontWeight: FontWeight.w600,
),
),
),
),
],
),
),
);
},
);
}
/// 💵 Helper for payment summary row
Widget _buildPaymentRow(String label, String value,
{bool highlight = false, double? size}) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: GoogleFonts.poppins(
fontSize: 12.sp,
fontWeight: FontWeight.w400,
color: const Color(0xff2D3134),
),
),
Container(
decoration: highlight
? BoxDecoration(
color: const Color(0xffFDCDCE),
borderRadius: BorderRadius.circular(6),
border: Border.all(color: Color(0xffEDEDED))
)
: null,
padding:
EdgeInsets.symmetric(horizontal: highlight ? 6 : 0, vertical: 2),
child: Text(
value,
style: GoogleFonts.poppins(
fontSize: size ?? 12.sp,
fontWeight: FontWeight.w400,
color: const Color(0xff2D3134),
),
),
),
],
);
}
}

View File

@@ -1,4 +1,7 @@
import 'package:citycards_customer/postcard/views/add_filter_step_page_view.dart';
import 'package:citycards_customer/postcard/views/order_postcard_preview_page_view.dart';
import 'package:citycards_customer/postcard/views/postcard_checkout_page_view.dart';
import 'package:citycards_customer/postcard/views/postcard_purchase_form_page_view.dart';
import 'package:citycards_customer/postcard/views/preview_postcard_step_page_view.dart';
import 'package:citycards_customer/postcard/views/upload_photo_step_page_view.dart';
import 'package:citycards_customer/postcard/views/write_message_step_page_view.dart';
@@ -7,6 +10,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import '../blocs/postcard_creation_bloc.dart';
import '../blocs/postcard_creation_state.dart';
import 'my_orders_page_view.dart';
import 'order_success_page_view.dart';
class PostcardCreationPage extends StatelessWidget {
const PostcardCreationPage({super.key});
@@ -31,9 +36,21 @@ class PostcardCreationPage extends StatelessWidget {
case PostcardStep.preview:
stepWidget = const PreviewPostcardStepPageView();
break;
default:
stepWidget = const UploadPhotoStepPageView();
}
case PostcardStep.purchase:
stepWidget = const PostcardPurchaseFormPageView();
break;
case PostcardStep.checkout:
stepWidget = const PostcardCheckoutPageView();
break;
case PostcardStep.orderSuccess:
stepWidget = const OrderSuccessPageView();
break;
case PostcardStep.myOrders:
stepWidget = const MyOrdersPageView();
break;
case PostcardStep.myOrderPostcardPreview:
stepWidget = const OrderPostcardPreviewPageView();
}
return Scaffold(
backgroundColor: Colors.white,

View File

@@ -0,0 +1,262 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../common_packages/app_bar.dart';
import '../blocs/postcard_creation_bloc.dart';
import '../blocs/postcard_creation_events.dart';
import '../blocs/postcard_creation_state.dart';
class PostcardPurchaseFormPageView extends StatelessWidget {
const PostcardPurchaseFormPageView({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<PostcardCreationBloc, PostcardCreationState>(
builder: (context, state) {
final bloc = context.read<PostcardCreationBloc>();
return SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CommonAppBar(isWhiteLogo: false, isProfilePage: false),
// Order ID
Text(
"#78895436",
style: TextStyle(
fontSize: 20.sp,
fontWeight: FontWeight.w600,
color: const Color(0xff1A1A1A),
),
),
const SizedBox(height: 20),
// Postcard image + title
Row(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: state.imagePath != null
? Image.file(
File(state.imagePath!),
height: 70,
width: 70,
fit: BoxFit.cover,
)
: Container(
height: 70,
width: 70,
color: const Color(0xffFEE7E7),
child: const Icon(Icons.image_outlined,
color: Color(0xffFDCDCE)),
),
),
const SizedBox(width: 16),
Expanded(
child: TextField(
decoration: InputDecoration(
hintText: "Add title",
hintStyle: GoogleFonts.poppins(
color: const Color(0xff999999), fontSize: 14.sp),
enabledBorder: const UnderlineInputBorder(
borderSide:
BorderSide(color: Color(0xffFDCDCE), width: 1),
),
focusedBorder: const UnderlineInputBorder(
borderSide:
BorderSide(color: Color(0xffFDCDCE), width: 1),
),
),
style: GoogleFonts.poppins(fontSize: 14.sp),
onChanged: (val) {
// You can dispatch event here: bloc.add(UpdateTitle(val));
},
),
),
],
),
const SizedBox(height: 28),
// Personal details section
Text(
"Add personal details",
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: const Color(0xff1A1A1A),
),
),
const SizedBox(height: 16),
_buildInputField(
label: "Full Name",
hint: "Lorem Ipsum",
),
_buildInputField(
label: "Email ID",
hint: "Lorem@gmail.com",
icon: Icons.email_outlined,
),
_buildInputField(
label: "Phone number",
hint: "+91 9999 999 999",
icon: Icons.phone_outlined,
),
const SizedBox(height: 28),
// Address details section
Text(
"Add address details",
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: const Color(0xff1A1A1A),
),
),
const SizedBox(height: 16),
_buildInputField(label: "City", hint: "Lorem Ipsum"),
_buildDropdownField(label: "Country", hint: "Lorem Ipsum"),
_buildDropdownField(label: "State", hint: "Lorem Ipsum"),
_buildInputField(label: "Zip Code", hint: "000000"),
const SizedBox(height: 30),
// Next Button
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
bloc.add(GoToNextStep());
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xffF95F62),
padding: EdgeInsets.symmetric(vertical: 16.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(40),
),
),
child: Text(
"Next",
style: TextStyle(
color: Colors.white,
fontSize: 14.sp,
fontWeight: FontWeight.w600,
),
),
),
),
],
),
),
);
},
);
}
/// 🔹 Reusable text field widget
Widget _buildInputField({
required String label,
required String hint,
IconData? icon,
}) {
return Padding(
padding: const EdgeInsets.only(bottom: 18),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: GoogleFonts.poppins(
fontSize: 13.sp,
fontWeight: FontWeight.w500,
color: const Color(0xff1A1A1A),
),
),
const SizedBox(height: 6),
TextField(
decoration: InputDecoration(
hintText: hint,
hintStyle: GoogleFonts.poppins(
color: const Color(0xff999999),
fontSize: 14.sp,
),
suffixIcon: icon != null
? Icon(icon, color: Colors.black, size: 20)
: null,
contentPadding:
const EdgeInsets.symmetric(vertical: 14, horizontal: 12),
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Color(0xffFDCDCE)),
borderRadius: BorderRadius.circular(8),
),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Color(0xffFDCDCE)),
borderRadius: BorderRadius.circular(8),
),
),
),
],
),
);
}
/// 🔹 Dropdown input
Widget _buildDropdownField({
required String label,
required String hint,
}) {
return Padding(
padding: const EdgeInsets.only(bottom: 18),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: GoogleFonts.poppins(
fontSize: 13.sp,
fontWeight: FontWeight.w500,
color: const Color(0xff1A1A1A),
),
),
const SizedBox(height: 6),
DropdownButtonFormField<String>(
value: null,
decoration: InputDecoration(
contentPadding:
const EdgeInsets.symmetric(vertical: 14, horizontal: 12),
enabledBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Color(0xffFDCDCE)),
borderRadius: BorderRadius.circular(8),
),
focusedBorder: OutlineInputBorder(
borderSide: const BorderSide(color: Color(0xffFDCDCE)),
borderRadius: BorderRadius.circular(8),
),
),
icon: const Icon(Icons.keyboard_arrow_down,
color: Color(0xffFDCDCE)),
hint: Text(
hint,
style: GoogleFonts.poppins(
color: const Color(0xff999999),
fontSize: 14.sp,
),
),
items: const [
DropdownMenuItem(value: "Lorem Ipsum", child: Text("Lorem Ipsum")),
],
onChanged: (val) {},
),
],
),
);
}
}

View File

@@ -1,4 +1,5 @@
import 'dart:io';
import 'package:citycards_customer/postcard/views/write_message_step_page_view.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
@@ -7,6 +8,7 @@ import 'package:google_fonts/google_fonts.dart';
import '../../common_packages/app_bar.dart';
import '../blocs/postcard_creation_bloc.dart';
import '../blocs/postcard_creation_state.dart';
import '../widgets/postcard_preview_widget.dart';
import '../widgets/purchase_details_bottom_sheet.dart';
import '../widgets/step_progressbar.dart';
@@ -19,7 +21,7 @@ class PreviewPostcardStepPageView extends StatefulWidget {
class _PreviewPostcardStepPageViewState extends State<PreviewPostcardStepPageView> {
bool showImage = false; // ✅ tracks which side is visible
bool showImage = false;
@override
Widget build(BuildContext context) {
@@ -55,51 +57,10 @@ class _PreviewPostcardStepPageViewState extends State<PreviewPostcardStepPageVie
fit: BoxFit.cover,
),
):
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFFFFF5F5),
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 🖼️ Image section
if (state.imagePath != null)
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.file(
File(state.imagePath!),
height: 140.h,
width: 140.w,
fit: BoxFit.cover,
),
),
const SizedBox(height: 12),
// 📝 Message section with lines and font
CustomPaint(
painter: _LinedPaperPainter(),
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 12,
),
child: Text(
state.message ?? "",
style: TextStyle(
fontFamily: state.selectedFont ??
GoogleFonts.poppins().fontFamily,
fontSize: 14.sp,
color: const Color(0xff1A1A1A),
height: 1.8,
),
),
),
),
],
),
PostCardPreviewWidget(
imagePath: state.imagePath ?? "",
message: state.message ?? "",
selectedFont: state.selectedFont,
),
const SizedBox(height: 24),
@@ -181,21 +142,3 @@ class _PreviewPostcardStepPageViewState extends State<PreviewPostcardStepPageVie
);
}
}
/// 📜 Custom Painter for lined background
class _LinedPaperPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = const Color(0xffE6DCDC)
..strokeWidth = 1;
const lineHeight = 30.0;
for (double y = 20; y < size.height; y += lineHeight) {
canvas.drawLine(Offset(0, y), Offset(size.width, y), paint);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

View File

@@ -6,6 +6,7 @@ import '../../common_packages/app_bar.dart';
import '../blocs/postcard_creation_bloc.dart';
import '../blocs/postcard_creation_events.dart';
import '../blocs/postcard_creation_state.dart';
import '../widgets/postcard_preview_widget.dart';
import '../widgets/step_progressbar.dart';
class WriteMessageStepPageView extends StatefulWidget {
@@ -81,7 +82,7 @@ class _WriteMessageStepPageViewState extends State<WriteMessageStepPageView> {
borderRadius: BorderRadius.circular(12),
),
child: CustomPaint(
painter: _LinedPaperPainter(),
painter: LinedPaperPainter(),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: TextField(
@@ -210,20 +211,3 @@ class _WriteMessageStepPageViewState extends State<WriteMessageStepPageView> {
}
}
/// 🖋 Custom Painter for horizontal lines
class _LinedPaperPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = const Color(0xffE6DCDC)
..strokeWidth = 1;
const lineHeight = 36.0; // distance between lines
for (double y = 28; y < size.height; y += lineHeight) {
canvas.drawLine(Offset(0, y), Offset(size.width, y), paint);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

View File

@@ -0,0 +1,84 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:google_fonts/google_fonts.dart';
class PostCardPreviewWidget extends StatelessWidget {
final String imagePath;
final String message;
final String? selectedFont;
const PostCardPreviewWidget({super.key, required this.imagePath, required this.message, this.selectedFont});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFFFFF5F5),
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.file(
File(imagePath),
height: 140.h,
width: 140.w,
fit: BoxFit.cover,
),
),
const SizedBox(height: 12),
CustomPaint(
painter: LinedPaperPainter(lineHeight: 28.0, topPadding: 38.0),
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 12,
),
child: Text(
message ?? "",
style: TextStyle(
fontFamily: selectedFont ??
GoogleFonts.poppins().fontFamily,
fontSize: 16.sp,
color: const Color(0xff1A1A1A),
height: 1.6,
),
),
),
),
],
),
);
}
}
/// 🖋 Custom Painter for horizontal lines
class LinedPaperPainter extends CustomPainter {
final double lineHeight;
final double topPadding;
LinedPaperPainter({this.lineHeight = 26.0, this.topPadding = 18.0});
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = const Color(0xffE6DCDC)
..strokeWidth = 1;
// Draw lines spaced evenly based on text line height
for (double y = topPadding; y < size.height; y += lineHeight) {
canvas.drawLine(Offset(0, y), Offset(size.width, y), paint);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

View File

@@ -22,6 +22,7 @@ class PurchaseDetailsBottomSheet {
child: BlocBuilder<PostcardCreationBloc, PostcardCreationState>(
builder: (context, state) {
final bloc = context.read<PostcardCreationBloc>();
return Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
@@ -106,7 +107,8 @@ class PurchaseDetailsBottomSheet {
),
ElevatedButton(
onPressed: () {
// TODO: Navigate to edit details screen
PurchaseDetailsBottomSheet.close(context);
bloc.add(GoToNextStep());
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xffF95F62),
@@ -180,4 +182,8 @@ class PurchaseDetailsBottomSheet {
},
);
}
static void close(BuildContext context) {
Navigator.of(context).pop();
}
}