worked on bu fix

This commit is contained in:
vishal-kaklotar-wdi
2025-11-19 11:26:23 +05:30
parent b54739953a
commit 1ca940e5cf
25 changed files with 1384 additions and 807 deletions

1
assets/intro/animm.json Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -24,6 +24,11 @@ PODS:
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- url_launcher_ios (0.0.1):
- Flutter
- video_player_avfoundation (0.0.1):
- Flutter
- FlutterMacOS
DEPENDENCIES:
- Flutter (from `Flutter`)
@@ -34,6 +39,8 @@ DEPENDENCIES:
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
SPEC REPOS:
trunk:
@@ -57,6 +64,10 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
video_player_avfoundation:
:path: ".symlinks/plugins/video_player_avfoundation/darwin"
SPEC CHECKSUMS:
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
@@ -69,6 +80,8 @@ SPEC CHECKSUMS:
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a
PODFILE CHECKSUM: 1857a7cdb7dfafe45f2b0e9a9af44644190f7506

View File

@@ -316,14 +316,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
@@ -374,14 +370,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";

View File

@@ -0,0 +1,15 @@
import 'package:citycards_customer/attraction_details/bloc/attraction_details_event.dart';
import 'package:citycards_customer/attraction_details/bloc/attraction_details_state.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class AttractionDetailsBloc
extends Bloc<AttractionDetailsEvent, AttractionDetailsState> {
AttractionDetailsBloc() : super(AttractionDetailsState()) {
on<SetFlowFromPass>(_setFlowFromPass);
}
void _setFlowFromPass(SetFlowFromPass event, Emitter<AttractionDetailsState> emit) {
print("Setting the flow from pass ${event.fromByPass}");
emit(state.copyWith(fromByPass: event.fromByPass));
}
}

View File

@@ -0,0 +1,9 @@
import 'package:equatable/equatable.dart';
abstract class AttractionDetailsEvent {
}
class SetFlowFromPass extends AttractionDetailsEvent{
final bool fromByPass;
SetFlowFromPass(this.fromByPass);
}

View File

@@ -0,0 +1,14 @@
import 'package:equatable/equatable.dart';
class AttractionDetailsState extends Equatable {
bool fromByPass;
AttractionDetailsState({this.fromByPass = false});
AttractionDetailsState copyWith({required bool fromByPass}) {
return AttractionDetailsState(fromByPass: fromByPass ?? this.fromByPass);
}
@override
List<Object?> get props => [fromByPass];
}

View File

@@ -1,8 +1,11 @@
import 'package:citycards_customer/attraction_details/bloc/attraction_details_bloc.dart';
import 'package:citycards_customer/attraction_details/bloc/attraction_details_state.dart';
import 'package:citycards_customer/attraction_details/view_model/attraction_details_view_model.dart';
import 'package:citycards_customer/attraction_details/widgets/share_bottomsheet.dart';
import 'package:citycards_customer/common_packages/app_bar.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 '../../core/route_constants.dart';
@@ -152,287 +155,319 @@ class AttractionDetailsView extends StatelessWidget {
// Booking Section
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"How to make a booking?",
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w400,
),
),
SizedBox(height: 16.h),
InkWell(
onTap: () =>
viewModel.makePhoneCall('+10123456789', context),
borderRadius: BorderRadius.circular(8.r),
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 12.w,
vertical: 12.h,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.r),
border: Border.all(color: Color(0xFFF95F62)),
),
child: Row(
children: [
Icon(
Icons.call,
color: Color(0xFFF95F62),
size: 32.w,
),
SizedBox(width: 16.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CustomText(
text: "Contact Number",
color: Colors.black.withOpacity(.6),
size: 12.sp,
weight: FontWeight.w500,
),
SizedBox(height: 6.h),
CustomText(
text: "+1012 3456 789",
color: Colors.black,
size: 14.sp,
weight: FontWeight.w600,
),
SizedBox(height: 6.h),
CustomText(
text: "Tap to call",
color: Colors.black.withOpacity(.4),
size: 12.sp,
weight: FontWeight.w400,
),
],
),
),
],
),
),
),
SizedBox(height: 16.h),
InkWell(
onTap: () =>
viewModel.sendEmail('CityCards24@gmail.com', context),
borderRadius: BorderRadius.circular(8.r),
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 12.w,
vertical: 12.h,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.r),
border: Border.all(color: Color(0xFFF95F62)),
),
child: Row(
children: [
Icon(
Icons.email_sharp,
color: Color(0xFFF95F62),
size: 32.w,
),
SizedBox(width: 16.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CustomText(
text: "Email",
color: Colors.black.withOpacity(.6),
size: 12.sp,
weight: FontWeight.w500,
),
SizedBox(height: 6.h),
CustomText(
text: "CityCards24@gmail.com",
color: Colors.black,
size: 14.sp,
weight: FontWeight.w600,
),
SizedBox(height: 6.h),
CustomText(
text: "Tap to email",
color: Colors.black.withOpacity(.4),
size: 12.sp,
weight: FontWeight.w400,
),
],
),
),
],
),
),
),
SizedBox(height: 16.h),
InkWell(
onTap: () {
Navigator.of(
context,
).pushNamed(RouteConstants.makeBooking);
},
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 24.w,
vertical: 18.h,
),
decoration: BoxDecoration(
color: Color(0xFFF95F62),
borderRadius: BorderRadius.circular(10.r),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CustomText(
text: "Via CityCards",
size: 16.sp,
weight: FontWeight.w500,
color: Colors.white,
),
SizedBox(height: 8.h),
CustomText(
text: "Create a booking via app",
size: 11.sp,
weight: FontWeight.w400,
color: Colors.white,
),
],
),
),
Icon(
Icons.arrow_forward_ios_outlined,
color: Colors.white,
),
],
),
),
),
SizedBox(height: 30.h),
Divider(color: Colors.black.withOpacity(0.2)),
SizedBox(height: 30.h),
Text(
"What is included",
style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.w500,
),
),
SizedBox(height: 4.h),
Wrap(
runSpacing: 16.h,
spacing: 16.w,
child: BlocBuilder<AttractionDetailsBloc, AttractionDetailsState>(
builder: (context, state) {
print("fajfasfasjfjas======= ${state.fromByPass}");
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
includedBox(
"assets/icons/bus.png",
"Bus",
"Transportation",
if (state.fromByPass)
Column(
children: [
Text(
"How to make a booking?",
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w400,
),
),
SizedBox(height: 16.h),
InkWell(
onTap: () => viewModel.makePhoneCall(
'+10123456789',
context,
),
borderRadius: BorderRadius.circular(8.r),
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 12.w,
vertical: 12.h,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.r),
border: Border.all(
color: Color(0xFFF95F62),
),
),
child: Row(
children: [
Icon(
Icons.call,
color: Color(0xFFF95F62),
size: 32.w,
),
SizedBox(width: 16.w),
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
CustomText(
text: "Contact Number",
color: Colors.black.withOpacity(
.6,
),
size: 12.sp,
weight: FontWeight.w500,
),
SizedBox(height: 6.h),
CustomText(
text: "+1012 3456 789",
color: Colors.black,
size: 14.sp,
weight: FontWeight.w600,
),
SizedBox(height: 6.h),
CustomText(
text: "Tap to call",
color: Colors.black.withOpacity(
.4,
),
size: 12.sp,
weight: FontWeight.w400,
),
],
),
),
],
),
),
),
SizedBox(height: 16.h),
InkWell(
onTap: () => viewModel.sendEmail(
'CityCards24@gmail.com',
context,
),
borderRadius: BorderRadius.circular(8.r),
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 12.w,
vertical: 12.h,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.r),
border: Border.all(
color: Color(0xFFF95F62),
),
),
child: Row(
children: [
Icon(
Icons.email_sharp,
color: Color(0xFFF95F62),
size: 32.w,
),
SizedBox(width: 16.w),
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
CustomText(
text: "Email",
color: Colors.black.withOpacity(
.6,
),
size: 12.sp,
weight: FontWeight.w500,
),
SizedBox(height: 6.h),
CustomText(
text: "CityCards24@gmail.com",
color: Colors.black,
size: 14.sp,
weight: FontWeight.w600,
),
SizedBox(height: 6.h),
CustomText(
text: "Tap to email",
color: Colors.black.withOpacity(
.4,
),
size: 12.sp,
weight: FontWeight.w400,
),
],
),
),
],
),
),
),
SizedBox(height: 16.h),
InkWell(
onTap: () {
Navigator.of(
context,
).pushNamed(RouteConstants.makeBooking);
},
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 24.w,
vertical: 18.h,
),
decoration: BoxDecoration(
color: Color(0xFFF95F62),
borderRadius: BorderRadius.circular(10.r),
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
CustomText(
text: "Via CityCards",
size: 16.sp,
weight: FontWeight.w500,
color: Colors.white,
),
SizedBox(height: 8.h),
CustomText(
text:
"Create a booking via app",
size: 11.sp,
weight: FontWeight.w400,
color: Colors.white,
),
],
),
),
Icon(
Icons.arrow_forward_ios_outlined,
color: Colors.white,
),
],
),
),
),
],
),
SizedBox(height: 30.h),
Divider(color: Colors.black.withOpacity(0.2)),
SizedBox(height: 30.h),
Text(
"What is included",
style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.w500,
),
),
includedBox(
"assets/icons/clock.png",
"2 day 1 night",
"Duration",
SizedBox(height: 4.h),
Wrap(
runSpacing: 16.h,
spacing: 16.w,
children: [
includedBox(
"assets/icons/bus.png",
"Bus",
"Transportation",
),
includedBox(
"assets/icons/clock.png",
"2 day 1 night",
"Duration",
),
includedBox(
"assets/icons/bx_qr.png",
"TAC200812695",
"Product code",
),
],
),
includedBox(
"assets/icons/bx_qr.png",
"TAC200812695",
"Product code",
SizedBox(height: 30.h),
Divider(color: Colors.black.withOpacity(0.2)),
SizedBox(height: 30.h),
Text(
"Exact Location",
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w400,
),
),
SizedBox(height: 8.h),
CustomText(
text: "View the location on map",
size: 12.sp,
color: Colors.black.withOpacity(.6),
),
SizedBox(height: 17.h),
ClipRRect(
borderRadius: BorderRadius.circular(13.54.r),
child: Image.asset(
height: 178.7.h,
width: double.infinity,
"assets/images/attra_detail_map.png",
fit: BoxFit.cover,
),
),
SizedBox(height: 17.h),
CustomText(
text:
"Angkor Mails Hotel \nNR6, Krong Siem Reap Cambodia",
size: 12.sp,
color: Colors.black.withOpacity(0.6),
),
SizedBox(height: 30.h),
Divider(color: Colors.black.withOpacity(0.2)),
SizedBox(height: 30.h),
Text(
"People frequently ask",
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w400,
),
),
SizedBox(height: 15.h),
faqBox(
"About this place",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. A id diam nisl, non justo, in odio...",
() {},
),
SizedBox(height: 15.h),
faqBox(
"Term and condition",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. A id diam nisl, non justo, in odio...",
() {
Navigator.pushNamed(
context,
RouteConstants.termsAndCondition,
);
},
),
SizedBox(height: 15.h),
faqBox(
"Cancellation Policy",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. A id diam nisl, non justo, in odio...",
() {},
),
],
),
SizedBox(height: 30.h),
Divider(color: Colors.black.withOpacity(0.2)),
SizedBox(height: 30.h),
Text(
"Exact Location",
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w400,
),
),
SizedBox(height: 8.h),
CustomText(
text: "View the location on map",
size: 12.sp,
color: Colors.black.withOpacity(.6),
),
SizedBox(height: 17.h),
ClipRRect(
borderRadius: BorderRadius.circular(13.54.r),
child: Image.asset(
height: 178.7.h,
width: double.infinity,
"assets/images/attra_detail_map.png",
fit: BoxFit.cover,
),
),
SizedBox(height: 17.h),
CustomText(
text:
"Angkor Mails Hotel \nNR6, Krong Siem Reap Cambodia",
size: 12.sp,
color: Colors.black.withOpacity(0.6),
),
SizedBox(height: 30.h),
Divider(color: Colors.black.withOpacity(0.2)),
SizedBox(height: 30.h),
Text(
"People frequently ask",
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w400,
),
),
SizedBox(height: 15.h),
faqBox(
"About this place",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. A id diam nisl, non justo, in odio...",
() {},
),
SizedBox(height: 15.h),
faqBox(
"Term and condition",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. A id diam nisl, non justo, in odio...",
() {
Navigator.pushNamed(
context,
RouteConstants.termsAndCondition,
);
},
),
SizedBox(height: 15.h),
faqBox(
"Cancellation Policy",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. A id diam nisl, non justo, in odio...",
() {},
),
],
);
},
),
),

View File

@@ -1,8 +1,11 @@
import 'package:citycards_customer/attraction_details/bloc/attraction_details_bloc.dart';
import 'package:citycards_customer/common_packages/app_bar.dart';
import 'package:citycards_customer/common_packages/back_widget.dart';
import 'package:citycards_customer/core/route_constants.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../attraction_details/bloc/attraction_details_event.dart';
import '../../common_packages/custom_search_field.dart';
import '../blocs/attractions_bloc.dart';
import '../repository/attractions_repository.dart';
@@ -15,22 +18,27 @@ class AttractionsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (_) {
final bloc = AttractionsBloc(AttractionsRepository());
// 🔥 Trigger event based on source
if (source == "home") {
bloc.add(LoadAttractions());
} else {
print("QR Passss -=------------------");
context.read<AttractionDetailsBloc>().add(SetFlowFromPass(true));
bloc.add(LoadMyPassAttraction());
}
return bloc;
},
),
BlocProvider(create: (_) => AttractionDetailsBloc(),
return BlocProvider(
create: (_) {
final bloc = AttractionsBloc(AttractionsRepository());
// 🔥 Trigger event based on source
if (source == "home") {
bloc.add(LoadAttractions());
} else if (source == "qrPass") {
bloc.add(LoadMyPassAttraction());
}
return bloc;
},
)
],
child: BlocBuilder<AttractionsBloc, AttractionsState>(
builder: (context, state) {
final bloc = context.read<AttractionsBloc>();

View File

@@ -11,7 +11,12 @@ class AttractionCard extends StatelessWidget {
Widget build(BuildContext context) {
return InkWell(
onTap: (){
Navigator.of(context).pushNamed(RouteConstants.attractionDetails);
print("the value of from page this pushed ${ModalRoute.of(context)?.settings.arguments}");
Navigator.pushNamed(
context,
RouteConstants.attractionDetails,
arguments: ModalRoute.of(context)?.settings.arguments, // FORWARD
);
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 8),

View File

@@ -0,0 +1,63 @@
import 'package:flutter_bloc/flutter_bloc.dart';
/// EVENTS
abstract class EmailVerify {}
class SetEmailEvent extends EmailVerify {
final String email;
SetEmailEvent(this.email);
}
class CheckOtpFilled extends EmailVerify {
final bool isOtpFilled;
CheckOtpFilled(this.isOtpFilled);
}
class CheckIsLoggedIn extends EmailVerify{}
/// STATE
class EmailVerifyState {
final String email;
final bool isOtpFilled;
final bool loggedIn;
EmailVerifyState({
required this.email,
required this.isOtpFilled,
required this.loggedIn
});
EmailVerifyState copyWith({
String? email,
bool? isOtpFilled,
bool? loggedIn
}) {
return EmailVerifyState(
email: email ?? this.email,
isOtpFilled: isOtpFilled ?? this.isOtpFilled,
loggedIn: loggedIn ?? this.loggedIn
);
}
}
/// BLOC
class EmailVerifyBloc extends Bloc<EmailVerify, EmailVerifyState> {
EmailVerifyBloc()
: super(EmailVerifyState(email: "", isOtpFilled: false,
loggedIn: false
)) {
on<SetEmailEvent>((event, emit) {
emit(state.copyWith(email: event.email));
});
on<CheckOtpFilled>((event, emit) {
emit(state.copyWith(isOtpFilled: event.isOtpFilled));
});
on<CheckIsLoggedIn>((event,emit){
emit(state.copyWith(loggedIn: true));
});
}
}

View File

@@ -0,0 +1,15 @@
import 'package:shared_preferences/shared_preferences.dart';
class LocalAuth {
Future<void> setloggedIn(bool islogged)async{
final SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool('islogged', islogged);
}
Future<bool> getloggedIn()async{
final SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getBool('islogged') ?? false;
}
}

View File

@@ -1,3 +1,5 @@
import 'package:citycards_customer/checkout/bloc/email_verify_bloc.dart';
import 'package:citycards_customer/checkout/repository/auth_local_repository.dart';
import 'package:citycards_customer/checkout/widget/all_coupons_bottomsheet.dart';
import 'package:citycards_customer/checkout/widget/login_email_bottomsheet.dart';
import 'package:citycards_customer/common_packages/app_bar.dart';
@@ -5,6 +7,7 @@ import 'package:citycards_customer/common_packages/custom_filled_button.dart';
import 'package:citycards_customer/common_packages/custom_text.dart';
import 'package:citycards_customer/common_packages/custom_dashed_line.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class CheckoutView extends StatelessWidget {
@@ -12,356 +15,383 @@ class CheckoutView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: true,
backgroundColor: Colors.white,
body: SafeArea(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 20.w),
child: Column(
children: [
CommonAppBar(
isWhiteLogo: false,
isProfilePage: false,
showCart: false,
showDivider: true,
),
Row(
children: [
GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: Icon(Icons.arrow_back),
),
SizedBox(width: 8.w),
CustomText(text: "Checkout", size: 12.sp),
],
),
SizedBox(height: 22.h),
Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Color(0xFFF95FAF).withOpacity(0.2)),
borderRadius: BorderRadius.circular(8.r),
return BlocProvider(
create: (context) => EmailVerifyBloc(),
child: Scaffold(
resizeToAvoidBottomInset: true,
backgroundColor: Colors.white,
body: SafeArea(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 20.w),
child: Column(
children: [
CommonAppBar(
isWhiteLogo: false,
isProfilePage: false,
showCart: false,
showDivider: true,
),
Row(
children: [
GestureDetector(
onTap: () {
Navigator.pop(context);
},
child: Icon(Icons.arrow_back),
),
SizedBox(width: 8.w),
CustomText(text: "Checkout", size: 12.sp),
],
),
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,
),
),
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,
SizedBox(height: 22.h),
Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
color: Color(0xFFF95FAF).withOpacity(0.2),
),
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,
),
),
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: [
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: [
Row(
children: [
Image.asset(
'assets/icons/adult.png',
scale: 4,
),
SizedBox(width: 4.w),
CustomText(
text: "3 adults",
color: Color(0xFF8E8E8E),
size: 12.sp,
),
],
Image.asset(
"assets/icons/kid.png",
scale: 4,
),
SizedBox(width: 4.w),
CustomText(
text: "3 Kids",
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(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: 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),
),
],
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(
width: 35.w,
height: 123.h,
decoration: BoxDecoration(
color: Color(0xFFF95FAF),
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(8.r),
topRight: Radius.circular(8.r),
),
padding: EdgeInsets.symmetric(
horizontal: 20.w,
vertical: 10.h,
),
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,
),
),
],
),
),
),
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),
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,
),
SizedBox(height: 15.h),
DashedDivider(
color: Color(0xFFACACAC),
thickness: 1.h,
dashLength: 4,
dashSpace: 4,
),
child: Row(
SizedBox(height: 10.h),
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,
),
CustomText(text: "Subtotal", size: 14.sp),
CustomText(
text: "\$49.50",
size: 14.sp,
weight: FontWeight.w500,
),
],
),
),
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),
),
],
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,
),
),
CustomText(
text: "\$42.60",
size: 24.sp,
weight: FontWeight.w500,
),
],
),
const Spacer(),
CustomFilledButton(
onTap: () {
showModalBottomSheet(
backgroundColor: Colors.white,
context: context,
isScrollControlled: true,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(12.r),
],
),
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),
),
],
),
),
builder: (_) => const LoginEmailBottomsheet(),
);
},
width: double.infinity,
label: "Login to Checkout",
),
SizedBox(height: 25.h),
],
CustomText(
text: "\$42.60",
size: 24.sp,
weight: FontWeight.w500,
),
],
),
const Spacer(),
FutureBuilder(
future: LocalAuth().getloggedIn(),
builder: (context, snap) {
final isLoggedIn = snap.data ?? false;
return BlocBuilder<EmailVerifyBloc, EmailVerifyState>(
builder: (context, state){
return CustomFilledButton(
onTap: () async {
final rootContext = context;
showModalBottomSheet(
backgroundColor: Colors.white,
context: context,
isScrollControlled: true,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(12.r),
),
),
builder: (_) => BlocProvider(
create: (rootContext) => EmailVerifyBloc(),
child: LoginEmailBottomsheet(
rootContext: rootContext,
),
),
);
},
width: double.infinity,
label: isLoggedIn || state.loggedIn ? "Proceed to Checkouts" :"Login to Checkout",
);
},
);
},
),
SizedBox(height: 25.h),
],
),
),
),
),

View File

@@ -1,12 +1,35 @@
import 'package:citycards_customer/checkout/bloc/email_verify_bloc.dart';
import 'package:citycards_customer/checkout/widget/verify_otp_bottomsheet.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_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
class LoginEmailBottomsheet extends StatelessWidget {
const LoginEmailBottomsheet({super.key});
class LoginEmailBottomsheet extends StatefulWidget {
final BuildContext rootContext;
LoginEmailBottomsheet({super.key, required this.rootContext});
@override
State<LoginEmailBottomsheet> createState() => _LoginEmailBottomsheetState();
}
class _LoginEmailBottomsheetState extends State<LoginEmailBottomsheet> {
TextEditingController emailController = TextEditingController();
String? emailError;
bool emailValidate() {
final emailRegex = RegExp(r'^[\w\.-]+@[\w\.-]+\.\w+$');
setState(() {
emailError = !emailRegex.hasMatch(emailController.text.trim())
? "Invalid email format"
: null;
});
return emailError == null;
}
@override
Widget build(BuildContext context) {
@@ -25,7 +48,11 @@ class LoginEmailBottomsheet extends StatelessWidget {
children: [
Image.asset("assets/logo/logo_city_cards_orange.png", scale: 4),
SizedBox(height: 8.h),
CustomText(text: "Get Started", size: 18.sp, weight: FontWeight.w500),
CustomText(
text: "Get Started",
size: 18.sp,
weight: FontWeight.w500,
),
SizedBox(height: 42.h),
CustomText(
text: "Enter your email to begin your CityCards journey",
@@ -35,19 +62,49 @@ class LoginEmailBottomsheet extends StatelessWidget {
SizedBox(height: 12.h),
TextField(
controller: emailController,
onChanged: (val) {
final bloc = context.read<EmailVerifyBloc>();
setState(() => emailValidate());
bloc.add(SetEmailEvent(val));
},
decoration: InputDecoration(
errorText: emailError,
filled: true,
contentPadding: EdgeInsets.symmetric(vertical: 6.h),
fillColor: const Color(0xFFFFF5F5),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: const Color(0xFFBB474A), width: 0.4.w),
borderSide: BorderSide(
color: const Color(0xFFBB474A),
width: 0.4.w,
),
borderRadius: BorderRadius.circular(8.sp),
),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(
color: const Color(0xFFBB474A),
width: 0.4.w,
),
borderRadius: BorderRadius.circular(8.sp),
),
focusedErrorBorder: OutlineInputBorder(
borderSide: BorderSide(
color: const Color(0xFFBB474A),
width: 0.4.w,
),
borderRadius: BorderRadius.circular(8.sp),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: const Color(0xFFBB474A), width: 0.4.w),
borderSide: BorderSide(
color: const Color(0xFFBB474A),
width: 0.4.w,
),
borderRadius: BorderRadius.circular(8.sp),
),
prefixIcon: const Icon(Icons.email_outlined, color: Color(0xFFF95F62)),
prefixIcon: const Icon(
Icons.email_outlined,
color: Color(0xFFF95F62),
),
hintText: "john.doe@gmail.com",
hintStyle: TextStyle(
color: const Color(0xFF000000).withOpacity(0.6),
@@ -59,18 +116,23 @@ class LoginEmailBottomsheet extends StatelessWidget {
SizedBox(height: 38.h),
CustomFilledButton(
onTap: () {
Navigator.pop(context);
showModalBottomSheet(
context: context,
backgroundColor: Colors.white,
isScrollControlled: true,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(12.r),
if (emailValidate()) {
Navigator.pop(context);
showModalBottomSheet(
context: context,
backgroundColor: Colors.white,
isScrollControlled: true,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(12.r),
),
),
),
builder: (_) => VerifyOtpBottomsheet(),
);
builder: (_) => BlocProvider(
create: (rootcontext) => EmailVerifyBloc(),
child: VerifyOtpBottomsheet(),
),
);
}
},
label: "Continue",
width: double.infinity,
@@ -78,7 +140,7 @@ class LoginEmailBottomsheet extends StatelessWidget {
SizedBox(height: 20.h),
InkWell(
onTap: (){
onTap: () {
Navigator.of(context).pushNamed(RouteConstants.createAcct);
},
child: Text.rich(

View File

@@ -1,6 +1,9 @@
import 'package:citycards_customer/checkout/bloc/email_verify_bloc.dart';
import 'package:citycards_customer/checkout/repository/auth_local_repository.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_otp_text_field/flutter_otp_text_field.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
@@ -25,64 +28,88 @@ class VerifyOtpBottomsheet extends StatelessWidget {
mainAxisSize: MainAxisSize.min, // shrink to fit content
children: [
Image.asset("assets/logo/logo_city_cards_orange.png", scale: 4),
SizedBox(height: 8.h),
CustomText(
text: "Verify your phone",
size: 18.sp,
weight: FontWeight.w500,
),
SizedBox(height: 42.h),
Text.rich(
TextSpan(
children: [
BlocBuilder<EmailVerifyBloc, EmailVerifyState>(
builder: (context, state) {
return Text.rich(
TextSpan(
text: "Enter the verification code sent to your email id",
style: TextStyle(
fontSize: 14.sp,
color: Colors.black.withOpacity(0.6),
),
children: [
TextSpan(
text:
"Enter the verification code sent to your email id: ",
style: TextStyle(
fontSize: 14.sp,
color: Colors.black.withOpacity(0.6),
),
),
TextSpan(
text: " ${state.email}",
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w500,
color: Colors.black,
),
),
],
),
TextSpan(
text: " frank7824@mail.com",
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w500,
color: Colors.black,
),
),
],
),
);
},
),
SizedBox(height: 15.h),
OtpTextField(
numberOfFields: 6,
borderWidth: 0.4.w,
fieldWidth: 48.w,
fieldHeight: 60.h,
borderRadius: BorderRadius.circular(8.r),
filled: true,
fillColor: const Color(0xFFFFF5F5),
borderColor: const Color(0xFFBB474A),
cursorColor: const Color(0xFFF95F62),
showFieldAsBox: true,
textStyle: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w500,
),
onCodeChanged: (code) {},
onSubmit: (code) {
debugPrint("OTP entered: $code");
BlocBuilder<EmailVerifyBloc, EmailVerifyState>(
builder: (context, state) {
final bloc = (context).read<EmailVerifyBloc>();
return OtpTextField(
numberOfFields: 6,
borderWidth: 0.4.w,
fieldWidth: 48.w,
fieldHeight: 60.h,
borderRadius: BorderRadius.circular(8.r),
filled: true,
fillColor: const Color(0xFFFFF5F5),
borderColor: const Color(0xFFBB474A),
cursorColor: const Color(0xFFF95F62),
showFieldAsBox: true,
textStyle: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w500,
),
onCodeChanged: (code) {},
onSubmit: (code) {
bloc.add(CheckOtpFilled(true));
debugPrint("OTP entered: $code");
},
);
},
),
SizedBox(height: 42.h),
CustomFilledButton(
onTap: () {
Navigator.pop(context);
BlocBuilder<EmailVerifyBloc, EmailVerifyState>(
builder: (context, state) {
return CustomFilledButton(
onTap: () async {
if (state.isOtpFilled) {
Navigator.pop(context);
await LocalAuth().setloggedIn(true);
context.read<EmailVerifyBloc>().add(CheckIsLoggedIn());
}
},
label: "Continue",
width: double.infinity,
);
},
label: "Continue",
width: double.infinity,
),
SizedBox(height: 20.h),

View File

@@ -18,6 +18,7 @@ import 'package:citycards_customer/itinerary_creation/views/itinerary_creation_v
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/postcard/views/postcard_creation_page_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';
@@ -231,6 +232,12 @@ class AppRouter {
return RegisteredUserHomePage();
},
);
case RouteConstants.postCardCreationPage:
return MaterialPageRoute(builder: (_){
return PostcardCreationPage();
});
default:
return MaterialPageRoute(
builder: (_) =>

View File

@@ -45,7 +45,7 @@ Widget buildOffstageNavigator(
return IntroScreensView();
});
// 🔹 Attractions Page
// 🔹 Attractions PageF
case RouteConstants.attractionsPage:
final args = settings.arguments as String;
return MaterialPageRoute(
@@ -93,7 +93,7 @@ Widget buildOffstageNavigator(
);
// 🔹 Upload Photo Page (start of postcard creation flow)
case RouteConstants.uploadPhotoPage:
case RouteConstants.postCardCreationPage:
return MaterialPageRoute(
builder: (_) => BlocProvider(
create: (_) => PostcardCreationBloc(),

View File

@@ -56,4 +56,5 @@ class RouteConstants {
static const String qrPage = '/qrPage';
static const String makeBooking = '/makeBooking';
static const String bookingSuccessful = '/bookingSuccessful';
static const String postCardCreationPage = '/postCardCreationPage';
}

View File

@@ -6,6 +6,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:google_fonts/google_fonts.dart';
import 'attraction_details/bloc/attraction_details_bloc.dart';
import 'core/app_router.dart';
import 'my_pass/blocs/my_pass_bloc.dart';
@@ -38,6 +39,9 @@ class MyApp extends StatelessWidget {
BlocProvider<MyPassBloc>(
create: (_) => MyPassBloc()..add(LoadMyPasses()),
),
BlocProvider<AttractionDetailsBloc>(
create: (_) => AttractionDetailsBloc(),
),
],
child: MaterialApp(
onGenerateRoute: _appRouter.onGenerateRoute,

View File

@@ -18,46 +18,44 @@ class PostcardCreationPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => PostcardCreationBloc(),
child: BlocBuilder<PostcardCreationBloc, PostcardCreationState>(
builder: (context, state) {
Widget stepWidget;
switch (state.currentStep) {
case PostcardStep.uploadPhoto:
stepWidget = const UploadPhotoStepPageView();
break;
case PostcardStep.addFilter:
stepWidget = const AddFilterStepPageView();
break;
case PostcardStep.writeMessage:
stepWidget = const WriteMessageStepPageView();
break;
case PostcardStep.preview:
stepWidget = const PreviewPostcardStepPageView();
break;
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 BlocBuilder<PostcardCreationBloc, PostcardCreationState>(
builder: (context, state) {
Widget stepWidget;
switch (state.currentStep) {
case PostcardStep.uploadPhoto:
stepWidget = const UploadPhotoStepPageView();
break;
case PostcardStep.addFilter:
stepWidget = const AddFilterStepPageView();
break;
case PostcardStep.writeMessage:
stepWidget = const WriteMessageStepPageView();
break;
case PostcardStep.preview:
stepWidget = const PreviewPostcardStepPageView();
break;
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();
break;
}
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(child: stepWidget),
);
},
),
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(child: stepWidget),
);
},
);
}
}

View File

@@ -83,7 +83,7 @@ class PostcardPage extends StatelessWidget {
),
),
onPressed: () {
Navigator.of(context).pushNamed(RouteConstants.uploadPhotoPage);
Navigator.of(context).pushNamed(RouteConstants.postCardCreationPage);
},
child: Text(
"Lets Create",

View File

@@ -8,13 +8,71 @@ import '../blocs/postcard_creation_bloc.dart';
import '../blocs/postcard_creation_events.dart';
import '../blocs/postcard_creation_state.dart';
class PostcardPurchaseFormPageView extends StatelessWidget {
class PostcardPurchaseFormPageView extends StatefulWidget {
const PostcardPurchaseFormPageView({super.key});
@override
State<PostcardPurchaseFormPageView> createState() =>
_PostcardPurchaseFormPageViewState();
}
class _PostcardPurchaseFormPageViewState
extends State<PostcardPurchaseFormPageView> {
/// Controllers
final titleCtrl = TextEditingController();
final fullNameCtrl = TextEditingController();
final emailCtrl = TextEditingController();
final phoneCtrl = TextEditingController();
final cityCtrl = TextEditingController();
final zipCtrl = TextEditingController();
String? country;
String? stateName;
/// Error messages
String? titleError;
String? fullNameError;
String? emailError;
String? phoneError;
String? cityError;
String? countryError;
String? stateError;
String? zipError;
bool validate() {
final emailRegex = RegExp(r'^[\w\.-]+@[\w\.-]+\.\w+$');
setState(() {
titleError = titleCtrl.text.isEmpty ? "Required" : null;
fullNameError = fullNameCtrl.text.isEmpty ? "Required" : null;
emailError = !emailRegex.hasMatch(emailCtrl.text.trim())
? "Invalid email format"
: null;
phoneError = phoneCtrl.text.length < 10 ? "Invalid phone" : null;
cityError = cityCtrl.text.isEmpty ? "Required" : null;
countryError = country == null ? "Required" : null;
stateError = stateName == null ? "Required" : null;
zipError = zipCtrl.text.length < 4 ? "Invalid zip" : null;
});
return titleError == null &&
fullNameError == null &&
emailError == null &&
phoneError == null &&
cityError == null &&
countryError == null &&
stateError == null &&
zipError == null;
}
@override
Widget build(BuildContext context) {
return BlocBuilder<PostcardCreationBloc, PostcardCreationState>(
builder: (context, state) {
final bloc = context.read<PostcardCreationBloc>();
return SafeArea(
@@ -23,8 +81,7 @@ class PostcardPurchaseFormPageView extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CommonAppBar(isWhiteLogo: false, isProfilePage: false, showDivider: true,),
CommonAppBar(isWhiteLogo: false, isProfilePage: false, showDivider: true),
// Order ID
Text(
"#78895436",
@@ -36,7 +93,7 @@ class PostcardPurchaseFormPageView extends StatelessWidget {
),
const SizedBox(height: 20),
// Postcard image + title
// Title
Row(
children: [
ClipRRect(
@@ -58,24 +115,38 @@ class PostcardPurchaseFormPageView extends StatelessWidget {
),
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),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
controller: titleCtrl,
onChanged: (_) {
setState(() => titleError = null);
},
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),
),
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));
},
if (titleError != null)
Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(titleError!,
style: const TextStyle(
color: Colors.red, fontSize: 12)),
),
],
),
),
],
@@ -83,7 +154,7 @@ class PostcardPurchaseFormPageView extends StatelessWidget {
const SizedBox(height: 28),
// Personal details section
// Personal details
Text(
"Add personal details",
style: TextStyle(
@@ -97,21 +168,45 @@ class PostcardPurchaseFormPageView extends StatelessWidget {
_buildInputField(
label: "Full Name",
hint: "Lorem Ipsum",
controller: fullNameCtrl,
errorText: fullNameError,
onChanged: (_) {
setState(() => fullNameError = null);
},
),
_buildInputField(
label: "Email ID",
hint: "Lorem@gmail.com",
icon: Icons.email_outlined,
controller: emailCtrl,
errorText: emailError,
type: TextInputType.emailAddress,
onChanged: (_) {
setState(() => emailError = null);
},
),
_buildInputField(
label: "Phone number",
hint: "+91 9999 999 999",
icon: Icons.phone_outlined,
controller: phoneCtrl,
errorText: phoneError,
onChanged: (value) {
if (value.length > 10) {
phoneCtrl.text = value.substring(0, 10);
phoneCtrl.selection = TextSelection.fromPosition(
TextPosition(offset: phoneCtrl.text.length),
);
}
setState(() => phoneError = null);
},
type: TextInputType.number
),
const SizedBox(height: 28),
// Address details section
Text(
"Add address details",
style: TextStyle(
@@ -122,10 +217,51 @@ class PostcardPurchaseFormPageView extends StatelessWidget {
),
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"),
_buildInputField(
label: "City",
hint: "Lorem Ipsum",
controller: cityCtrl,
errorText: cityError,
onChanged: (_) {
setState(() => cityError = null);
},
),
_buildDropdownField(
label: "Country",
hint: "Lorem Ipsum",
value: country,
errorText: countryError,
onChanged: (val) {
setState(() {
country = val;
countryError = null;
});
},
),
_buildDropdownField(
label: "State",
hint: "Lorem Ipsum",
value: stateName,
errorText: stateError,
onChanged: (val) {
setState(() {
stateName = val;
stateError = null;
});
},
),
_buildInputField(
label: "Zip Code",
hint: "000000",
controller: zipCtrl,
errorText: zipError,
onChanged: (_) {
setState(() => zipError = null);
},
),
const SizedBox(height: 30),
@@ -134,7 +270,9 @@ class PostcardPurchaseFormPageView extends StatelessWidget {
width: double.infinity,
child: ElevatedButton(
onPressed: () {
bloc.add(GoToNextStep());
if (validate()) {
bloc.add(GoToNextStep());
}
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xffF95F62),
@@ -161,36 +299,39 @@ class PostcardPurchaseFormPageView extends StatelessWidget {
);
}
/// 🔹 Reusable text field widget
/// TEXT FIELD (NO UI CHANGES)
Widget _buildInputField({
required String label,
required String hint,
required TextEditingController controller,
required Function(String) onChanged,
String? errorText,
IconData? icon,
TextInputType? type
}) {
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),
),
),
Text(label,
style: GoogleFonts.poppins(
fontSize: 13.sp,
fontWeight: FontWeight.w500,
color: const Color(0xff1A1A1A))),
const SizedBox(height: 6),
TextField(
controller: controller,
onChanged: onChanged,
keyboardType: type,
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,
suffixIcon:
icon != null ? Icon(icon, color: Colors.black, size: 20) : null,
contentPadding:
const EdgeInsets.symmetric(vertical: 14, horizontal: 12),
enabledBorder: OutlineInputBorder(
@@ -203,32 +344,38 @@ class PostcardPurchaseFormPageView extends StatelessWidget {
),
),
),
if (errorText != null)
Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(errorText!,
style: const TextStyle(color: Colors.red, fontSize: 12)),
),
],
),
);
}
/// 🔹 Dropdown input
/// DROPDOWN FIELD (NO UI CHANGES)
Widget _buildDropdownField({
required String label,
required String hint,
required String? value,
required Function(String?) onChanged,
String? errorText,
}) {
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),
),
),
Text(label,
style: GoogleFonts.poppins(
fontSize: 13.sp,
fontWeight: FontWeight.w500,
color: const Color(0xff1A1A1A))),
const SizedBox(height: 6),
DropdownButtonFormField<String>(
value: null,
value: value,
decoration: InputDecoration(
contentPadding:
const EdgeInsets.symmetric(vertical: 14, horizontal: 12),
@@ -253,8 +400,14 @@ class PostcardPurchaseFormPageView extends StatelessWidget {
items: const [
DropdownMenuItem(value: "Lorem Ipsum", child: Text("Lorem Ipsum")),
],
onChanged: (val) {},
onChanged: onChanged,
),
if (errorText != null)
Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(errorText!,
style: const TextStyle(color: Colors.red, fontSize: 12)),
),
],
),
);

View File

@@ -5,36 +5,171 @@ import 'package:google_fonts/google_fonts.dart';
class MessageCardWidget extends StatelessWidget {
final String message;
final String? selectedFont;
const MessageCardWidget({super.key, required this.message, this.selectedFont});
MessageCardWidget({super.key, required this.message, this.selectedFont});
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
children: [
Image.asset(
'assets/images/postcard_bg.png',
width: double.infinity,
fit: BoxFit.contain,
return Container(
constraints: BoxConstraints(minHeight: 227.h),
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 8.h),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.r),
gradient: const LinearGradient(
colors: [
Color(0xFFF5E9D7), // top-left shade
Color(0xFFE7D3B8), // bottom-right shade
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
boxShadow: [
BoxShadow(
color: Color(0xFFA08264).withOpacity(0.15),
blurRadius: 6,
spreadRadius: 0,
offset: Offset(-0.5, -0.5),
),
],
),
foregroundDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.r),
gradient: RadialGradient(
center: const Alignment(-0.6, -0.6),
radius: 1.2,
colors: [Color(0xFFBEA078).withOpacity(0.15), Colors.transparent],
),
),
Positioned(
right: 10,
top: 50,
child: SizedBox(
width: 150.w,
child: Text(message,
textAlign: TextAlign.left,
style: TextStyle(
fontFamily: selectedFont ??
GoogleFonts.poppins().fontFamily,
color: Colors.black,
fontSize: 10,
// ---------- IMPORTANT: IntrinsicHeight ensures children get bounded height ----------
child: IntrinsicHeight(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch, // stretch so children fill height
children: [
// ---------------- LEFT SECTION ---------------- //
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
// SpaceBetween ensures top content stays top and CityCards.co stays bottom
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Top group
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.asset(
"assets/logo/logo_city_cards.png",
height: 22.h,
),
SizedBox(height: 4.h),
Text(
"POSTCARD",
style: TextStyle(
fontSize: 6.sp,
letterSpacing: 0.93,
fontWeight: FontWeight.w600,
color: Color(0xFF000000).withOpacity(0.4),
),
),
SizedBox(height: 7.h),
Text(
"MESSAGE PREVIEW",
style: TextStyle(
fontSize: 5.sp,
letterSpacing: 0.93,
color: Color(0xFF000000).withOpacity(0.6),
),
),
SizedBox(height: 4.h),
Text(
message,
style: TextStyle(
fontFamily: selectedFont,
fontSize: 12.sp,
height: 1.6,
color: Color(0xFF0A0D13).withOpacity(0.8),
),
),
],
),
// Bottom text — will stay at the bottom of the left column
Text(
"CityCards.co",
style: TextStyle(fontSize: 12.sp, color: Color(0xFFF95F62)),
),
],
),
),
),
// ---------------- DIVIDER (middle) ---------------- //
Container(
width: 3.86.w,
height: double.infinity, // will match IntrinsicHeight-bounded Row height
margin: EdgeInsets.only(right: 10.w,top:24.h,bottom: 24.h),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Color(0xFF000000).withOpacity(0),
Color(0xFF65543F).withOpacity(0.4),
Color(0xFF65543F).withOpacity(0.6),
Color(0xFF65543F).withOpacity(0.8),
Color(0xFF65543F).withOpacity(0.6),
Color(0xFF65543F).withOpacity(0.4),
Color(0xFF000000).withOpacity(0),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
),
// ---------------- RIGHT SECTION ---------------- //
Align(
alignment: Alignment.center,
child: Container(
padding: EdgeInsets.all(7),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4.r),
border: Border.all(
color: Color(0xFF000000).withOpacity(0.12),
width: 1,
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"ADDRESS",
style: TextStyle(
fontSize: 5.sp,
fontWeight: FontWeight.w500,
letterSpacing: 0.93,
color: Color(0xFF000000).withOpacity(0.6),
),
),
Text(
"121 Saint Denis Street,\n"
"Louisiana,\n"
"United States of America",
style: TextStyle(
fontFamily: selectedFont,
fontSize: 8.sp,
height: 1.7.h,
),
textAlign: TextAlign.center,
),
],
),
),
),
],
),
],
),
);
}
}

View File

@@ -16,22 +16,13 @@ class PostCardPreviewWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
return SizedBox(
width: double.infinity,
height: 230.h,
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
gradient: LinearGradient(colors: [
Color(0xffE2D6C2),
Color(0xffFFF5E6),
Color(0xffFFF5E6),
]),
border: Border.all(
color: Color(0xff000000).withOpacity(0.12),
),
borderRadius: BorderRadius.circular(12),
),
child: Image.file(File(imagePath), fit: BoxFit.cover),
height: 227.h,
child: ClipRRect(
borderRadius: BorderRadius.circular(6.r),
child: Image.file(File(imagePath), fit: BoxFit.cover)),
);
}
}

View File

@@ -31,7 +31,7 @@ class _SplashScreenState extends State<SplashScreen> {
backgroundColor: const Color(0xFFF95F62), // Coral red background
body: Center(
child: Lottie.asset(
'assets/intro/animation.json', // Your Lottie file
'assets/intro/animm.json', // Your Lottie file
fit: BoxFit.cover,
repeat: true,
),