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

View File

@@ -316,14 +316,10 @@
inputFileListPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
); );
inputPaths = (
);
name = "[CP] Embed Pods Frameworks"; name = "[CP] Embed Pods Frameworks";
outputFileListPaths = ( outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
); );
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
@@ -374,14 +370,10 @@
inputFileListPaths = ( inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
); );
inputPaths = (
);
name = "[CP] Copy Pods Resources"; name = "[CP] Copy Pods Resources";
outputFileListPaths = ( outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
); );
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; 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/view_model/attraction_details_view_model.dart';
import 'package:citycards_customer/attraction_details/widgets/share_bottomsheet.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/app_bar.dart';
import 'package:citycards_customer/common_packages/custom_text.dart'; import 'package:citycards_customer/common_packages/custom_text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../core/route_constants.dart'; import '../../core/route_constants.dart';
@@ -152,287 +155,319 @@ class AttractionDetailsView extends StatelessWidget {
// Booking Section // Booking Section
Padding( Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w), padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Column( child: BlocBuilder<AttractionDetailsBloc, AttractionDetailsState>(
crossAxisAlignment: CrossAxisAlignment.start, builder: (context, state) {
children: [ print("fajfasfasjfjas======= ${state.fromByPass}");
Text( return Column(
"How to make a booking?", crossAxisAlignment: CrossAxisAlignment.start,
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,
children: [ children: [
includedBox( if (state.fromByPass)
"assets/icons/bus.png", Column(
"Bus", children: [
"Transportation", 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( SizedBox(height: 4.h),
"assets/icons/clock.png",
"2 day 1 night", Wrap(
"Duration", 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( SizedBox(height: 30.h),
"assets/icons/bx_qr.png",
"TAC200812695", Divider(color: Colors.black.withOpacity(0.2)),
"Product code", 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/app_bar.dart';
import 'package:citycards_customer/common_packages/back_widget.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/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../attraction_details/bloc/attraction_details_event.dart';
import '../../common_packages/custom_search_field.dart'; import '../../common_packages/custom_search_field.dart';
import '../blocs/attractions_bloc.dart'; import '../blocs/attractions_bloc.dart';
import '../repository/attractions_repository.dart'; import '../repository/attractions_repository.dart';
@@ -15,22 +18,27 @@ class AttractionsPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { 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>( child: BlocBuilder<AttractionsBloc, AttractionsState>(
builder: (context, state) { builder: (context, state) {
final bloc = context.read<AttractionsBloc>(); final bloc = context.read<AttractionsBloc>();

View File

@@ -11,7 +11,12 @@ class AttractionCard extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return InkWell( return InkWell(
onTap: (){ 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( child: Container(
margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 8), 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/all_coupons_bottomsheet.dart';
import 'package:citycards_customer/checkout/widget/login_email_bottomsheet.dart'; import 'package:citycards_customer/checkout/widget/login_email_bottomsheet.dart';
import 'package:citycards_customer/common_packages/app_bar.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_text.dart';
import 'package:citycards_customer/common_packages/custom_dashed_line.dart'; import 'package:citycards_customer/common_packages/custom_dashed_line.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
class CheckoutView extends StatelessWidget { class CheckoutView extends StatelessWidget {
@@ -12,356 +15,383 @@ class CheckoutView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return BlocProvider(
resizeToAvoidBottomInset: true, create: (context) => EmailVerifyBloc(),
backgroundColor: Colors.white, child: Scaffold(
body: SafeArea( resizeToAvoidBottomInset: true,
child: Padding( backgroundColor: Colors.white,
padding: EdgeInsets.symmetric(horizontal: 20.w), body: SafeArea(
child: Column( child: Padding(
children: [ padding: EdgeInsets.symmetric(horizontal: 20.w),
CommonAppBar( child: Column(
isWhiteLogo: false, children: [
isProfilePage: false, CommonAppBar(
showCart: false, isWhiteLogo: false,
showDivider: true, isProfilePage: false,
), showCart: false,
Row( showDivider: true,
children: [ ),
GestureDetector( Row(
onTap: () { children: [
Navigator.pop(context); GestureDetector(
}, onTap: () {
child: Icon(Icons.arrow_back), Navigator.pop(context);
), },
SizedBox(width: 8.w), child: Icon(Icons.arrow_back),
CustomText(text: "Checkout", size: 12.sp), ),
], 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),
), ),
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( SizedBox(height: 22.h),
width: MediaQuery.of(context).size.width * .5, Container(
child: Row( decoration: BoxDecoration(
mainAxisAlignment: color: Colors.white,
MainAxisAlignment.spaceBetween, 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: [ children: [
Row( Image.asset(
children: [ "assets/icons/kid.png",
Image.asset( scale: 4,
'assets/icons/adult.png', ),
scale: 4, SizedBox(width: 4.w),
), CustomText(
SizedBox(width: 4.w), text: "3 Kids",
CustomText( color: Color(0xFF8E8E8E),
text: "3 adults", size: 12.sp,
color: Color(0xFF8E8E8E),
size: 12.sp,
),
],
), ),
Row( SizedBox(width: 53.w),
children: [
Image.asset( CustomText(
'assets/icons/qty.png', text: "\$49.50",
scale: 4, size: 24.sp,
), weight: FontWeight.w500,
SizedBox(width: 4.w), color: Color(0xFFF95F62),
Text.rich( ),
TextSpan( ],
children: [ ),
TextSpan( ],
text: "Qty:", ),
style: TextStyle( ],
color: Color(0xFF8E8E8E), ),
fontSize: 12.sp,
), Container(
), width: 35.w,
TextSpan( height: 123.h,
text: " 2", decoration: BoxDecoration(
style: TextStyle( color: Color(0xFFF95FAF),
color: Color(0xFF000000), borderRadius: BorderRadius.only(
fontSize: 12.sp, bottomRight: Radius.circular(8.r),
fontWeight: FontWeight.w500, 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), SizedBox(height: 15.h),
Row( Container(
children: [ padding: EdgeInsets.symmetric(
Image.asset("assets/icons/kid.png", scale: 4), horizontal: 12.w,
SizedBox(width: 4.w), vertical: 12.h,
CustomText( ),
text: "3 Kids", decoration: BoxDecoration(
color: Color(0xFF8E8E8E), color: Color(0xFFFFF5F5),
size: 12.sp, borderRadius: BorderRadius.circular(8.r),
), border: Border.all(
color: Color(0xFFBB474A).withOpacity(0.4),
SizedBox(width: 53.w), width: 0.8,
),
CustomText( ),
text: "\$49.50", child: Row(
size: 24.sp, mainAxisAlignment: MainAxisAlignment.spaceBetween,
weight: FontWeight.w500, children: [
color: Color(0xFFF95F62), 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( Container(
width: 35.w, padding: EdgeInsets.symmetric(
height: 123.h, horizontal: 20.w,
decoration: BoxDecoration( vertical: 10.h,
color: Color(0xFFF95FAF),
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(8.r),
topRight: Radius.circular(8.r),
),
), ),
child: RotatedBox( decoration: BoxDecoration(
quarterTurns: -1, border: Border.all(color: Color(0xFFF95F62)),
child: Center( borderRadius: BorderRadius.circular(8.r),
child: RichText( ),
text: TextSpan( child: CustomText(
children: [ text: "Apply",
TextSpan( color: Color(0xFFF95F62),
text: "Flexi ", size: 14.sp,
style: TextStyle(
color: Colors.white,
fontSize: 16.sp,
),
),
TextSpan(
text: "Card",
style: TextStyle(
color: Colors.white,
fontSize: 12.sp,
),
),
],
),
),
),
), ),
), ),
], ],
), ),
), ),
),
SizedBox(height: 15.h), SizedBox(height: 15.h),
Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 12.h), DashedDivider(
decoration: BoxDecoration( color: Color(0xFFACACAC),
color: Color(0xFFFFF5F5), thickness: 1.h,
borderRadius: BorderRadius.circular(8.r), dashLength: 4,
border: Border.all( dashSpace: 4,
color: Color(0xFFBB474A).withOpacity(0.4),
width: 0.8,
),
), ),
child: Row( SizedBox(height: 10.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Column( CustomText(text: "Subtotal", size: 14.sp),
crossAxisAlignment: CrossAxisAlignment.start, CustomText(
children: [ text: "\$49.50",
CustomText( size: 14.sp,
text: "Get 10% off on your first trip", weight: FontWeight.w500,
color: Color(0xFF262626),
size: 14.sp,
),
SizedBox(height: 7.h),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
GestureDetector(
onTap: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(12.r),
),
),
builder: (_) => AllCouponsBottomsheet(),
);
},
child: CustomText(
text: "View all coupons",
color: Color(0xFFF95F62),
size: 12,
),
),
SizedBox(width: 3.w),
Icon(Icons.arrow_right, color: Color(0xFFF95F62)),
],
),
],
),
const Spacer(),
Container(
padding: EdgeInsets.symmetric(
horizontal: 20.w,
vertical: 10.h,
),
decoration: BoxDecoration(
border: Border.all(color: Color(0xFFF95F62)),
borderRadius: BorderRadius.circular(8.r),
),
child: CustomText(
text: "Apply",
color: Color(0xFFF95F62),
size: 14.sp,
),
), ),
], ],
), ),
), SizedBox(height: 14.h),
Row(
SizedBox(height: 15.h), mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
DashedDivider( CustomText(text: "Discount", size: 14.sp),
color: Color(0xFFACACAC), CustomText(
thickness: 1.h, text: "-7.20%",
dashLength: 4, size: 14.sp,
dashSpace: 4, weight: FontWeight.w500,
),
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),
),
],
), ),
), ],
CustomText( ),
text: "\$42.60", SizedBox(height: 10.h),
size: 24.sp, DashedDivider(
weight: FontWeight.w500, color: Color(0xFFACACAC),
), thickness: 1.h,
], dashLength: 4,
), dashSpace: 4,
const Spacer(), ),
CustomFilledButton( SizedBox(height: 10.h),
onTap: () {
showModalBottomSheet( Row(
backgroundColor: Colors.white, mainAxisAlignment: MainAxisAlignment.spaceBetween,
context: context, children: [
isScrollControlled: true, Expanded(
shape: RoundedRectangleBorder( child: Column(
borderRadius: BorderRadius.vertical( crossAxisAlignment: CrossAxisAlignment.start,
top: Radius.circular(12.r), 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(), CustomText(
); text: "\$42.60",
}, size: 24.sp,
width: double.infinity, weight: FontWeight.w500,
label: "Login to Checkout", ),
), ],
SizedBox(height: 25.h), ),
], 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/checkout/widget/verify_otp_bottomsheet.dart';
import 'package:citycards_customer/common_packages/custom_filled_button.dart'; 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_text.dart';
import 'package:citycards_customer/core/route_constants.dart'; import 'package:citycards_customer/core/route_constants.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
class LoginEmailBottomsheet extends StatelessWidget { class LoginEmailBottomsheet extends StatefulWidget {
const LoginEmailBottomsheet({super.key}); 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -25,7 +48,11 @@ class LoginEmailBottomsheet extends StatelessWidget {
children: [ children: [
Image.asset("assets/logo/logo_city_cards_orange.png", scale: 4), Image.asset("assets/logo/logo_city_cards_orange.png", scale: 4),
SizedBox(height: 8.h), 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), SizedBox(height: 42.h),
CustomText( CustomText(
text: "Enter your email to begin your CityCards journey", text: "Enter your email to begin your CityCards journey",
@@ -35,19 +62,49 @@ class LoginEmailBottomsheet extends StatelessWidget {
SizedBox(height: 12.h), SizedBox(height: 12.h),
TextField( TextField(
controller: emailController,
onChanged: (val) {
final bloc = context.read<EmailVerifyBloc>();
setState(() => emailValidate());
bloc.add(SetEmailEvent(val));
},
decoration: InputDecoration( decoration: InputDecoration(
errorText: emailError,
filled: true, filled: true,
contentPadding: EdgeInsets.symmetric(vertical: 6.h), contentPadding: EdgeInsets.symmetric(vertical: 6.h),
fillColor: const Color(0xFFFFF5F5), fillColor: const Color(0xFFFFF5F5),
enabledBorder: OutlineInputBorder( 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), borderRadius: BorderRadius.circular(8.sp),
), ),
focusedBorder: OutlineInputBorder( 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), 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", hintText: "john.doe@gmail.com",
hintStyle: TextStyle( hintStyle: TextStyle(
color: const Color(0xFF000000).withOpacity(0.6), color: const Color(0xFF000000).withOpacity(0.6),
@@ -59,18 +116,23 @@ class LoginEmailBottomsheet extends StatelessWidget {
SizedBox(height: 38.h), SizedBox(height: 38.h),
CustomFilledButton( CustomFilledButton(
onTap: () { onTap: () {
Navigator.pop(context); if (emailValidate()) {
showModalBottomSheet( Navigator.pop(context);
context: context, showModalBottomSheet(
backgroundColor: Colors.white, context: context,
isScrollControlled: true, backgroundColor: Colors.white,
shape: RoundedRectangleBorder( isScrollControlled: true,
borderRadius: BorderRadius.vertical( shape: RoundedRectangleBorder(
top: Radius.circular(12.r), borderRadius: BorderRadius.vertical(
top: Radius.circular(12.r),
),
), ),
), builder: (_) => BlocProvider(
builder: (_) => VerifyOtpBottomsheet(), create: (rootcontext) => EmailVerifyBloc(),
); child: VerifyOtpBottomsheet(),
),
);
}
}, },
label: "Continue", label: "Continue",
width: double.infinity, width: double.infinity,
@@ -78,7 +140,7 @@ class LoginEmailBottomsheet extends StatelessWidget {
SizedBox(height: 20.h), SizedBox(height: 20.h),
InkWell( InkWell(
onTap: (){ onTap: () {
Navigator.of(context).pushNamed(RouteConstants.createAcct); Navigator.of(context).pushNamed(RouteConstants.createAcct);
}, },
child: Text.rich( 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_filled_button.dart';
import 'package:citycards_customer/common_packages/custom_text.dart'; import 'package:citycards_customer/common_packages/custom_text.dart';
import 'package:flutter/material.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_otp_text_field/flutter_otp_text_field.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
@@ -25,64 +28,88 @@ class VerifyOtpBottomsheet extends StatelessWidget {
mainAxisSize: MainAxisSize.min, // shrink to fit content mainAxisSize: MainAxisSize.min, // shrink to fit content
children: [ children: [
Image.asset("assets/logo/logo_city_cards_orange.png", scale: 4), Image.asset("assets/logo/logo_city_cards_orange.png", scale: 4),
SizedBox(height: 8.h), SizedBox(height: 8.h),
CustomText( CustomText(
text: "Verify your phone", text: "Verify your phone",
size: 18.sp, size: 18.sp,
weight: FontWeight.w500, weight: FontWeight.w500,
), ),
SizedBox(height: 42.h), SizedBox(height: 42.h),
Text.rich(
TextSpan( BlocBuilder<EmailVerifyBloc, EmailVerifyState>(
children: [ builder: (context, state) {
return Text.rich(
TextSpan( TextSpan(
text: "Enter the verification code sent to your email id", children: [
style: TextStyle( TextSpan(
fontSize: 14.sp, text:
color: Colors.black.withOpacity(0.6), "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), SizedBox(height: 15.h),
OtpTextField( BlocBuilder<EmailVerifyBloc, EmailVerifyState>(
numberOfFields: 6, builder: (context, state) {
borderWidth: 0.4.w, final bloc = (context).read<EmailVerifyBloc>();
fieldWidth: 48.w, return OtpTextField(
fieldHeight: 60.h, numberOfFields: 6,
borderRadius: BorderRadius.circular(8.r), borderWidth: 0.4.w,
filled: true, fieldWidth: 48.w,
fillColor: const Color(0xFFFFF5F5), fieldHeight: 60.h,
borderColor: const Color(0xFFBB474A), borderRadius: BorderRadius.circular(8.r),
cursorColor: const Color(0xFFF95F62), filled: true,
showFieldAsBox: true, fillColor: const Color(0xFFFFF5F5),
textStyle: TextStyle( borderColor: const Color(0xFFBB474A),
fontSize: 18.sp, cursorColor: const Color(0xFFF95F62),
fontWeight: FontWeight.w500, showFieldAsBox: true,
), textStyle: TextStyle(
onCodeChanged: (code) {}, fontSize: 18.sp,
onSubmit: (code) { fontWeight: FontWeight.w500,
debugPrint("OTP entered: $code"); ),
onCodeChanged: (code) {},
onSubmit: (code) {
bloc.add(CheckOtpFilled(true));
debugPrint("OTP entered: $code");
},
);
}, },
), ),
SizedBox(height: 42.h), SizedBox(height: 42.h),
CustomFilledButton( BlocBuilder<EmailVerifyBloc, EmailVerifyState>(
onTap: () { builder: (context, state) {
Navigator.pop(context); 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), 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_empty_view.dart';
import 'package:citycards_customer/itinerary_creation/views/magic_itinerary_filled_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/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/privacy/privacy_view.dart';
import 'package:citycards_customer/search_offers/bloc/search_offers_listing_bloc.dart'; import 'package:citycards_customer/search_offers/bloc/search_offers_listing_bloc.dart';
import 'package:citycards_customer/search_offers/view/search_offers_with_listing.dart'; import 'package:citycards_customer/search_offers/view/search_offers_with_listing.dart';
@@ -231,6 +232,12 @@ class AppRouter {
return RegisteredUserHomePage(); return RegisteredUserHomePage();
}, },
); );
case RouteConstants.postCardCreationPage:
return MaterialPageRoute(builder: (_){
return PostcardCreationPage();
});
default: default:
return MaterialPageRoute( return MaterialPageRoute(
builder: (_) => builder: (_) =>

View File

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

View File

@@ -56,4 +56,5 @@ class RouteConstants {
static const String qrPage = '/qrPage'; static const String qrPage = '/qrPage';
static const String makeBooking = '/makeBooking'; static const String makeBooking = '/makeBooking';
static const String bookingSuccessful = '/bookingSuccessful'; 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_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'attraction_details/bloc/attraction_details_bloc.dart';
import 'core/app_router.dart'; import 'core/app_router.dart';
import 'my_pass/blocs/my_pass_bloc.dart'; import 'my_pass/blocs/my_pass_bloc.dart';
@@ -38,6 +39,9 @@ class MyApp extends StatelessWidget {
BlocProvider<MyPassBloc>( BlocProvider<MyPassBloc>(
create: (_) => MyPassBloc()..add(LoadMyPasses()), create: (_) => MyPassBloc()..add(LoadMyPasses()),
), ),
BlocProvider<AttractionDetailsBloc>(
create: (_) => AttractionDetailsBloc(),
),
], ],
child: MaterialApp( child: MaterialApp(
onGenerateRoute: _appRouter.onGenerateRoute, onGenerateRoute: _appRouter.onGenerateRoute,

View File

@@ -18,46 +18,44 @@ class PostcardCreationPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocBuilder<PostcardCreationBloc, PostcardCreationState>(
create: (_) => PostcardCreationBloc(), builder: (context, state) {
child: BlocBuilder<PostcardCreationBloc, PostcardCreationState>( Widget stepWidget;
builder: (context, state) { switch (state.currentStep) {
Widget stepWidget; case PostcardStep.uploadPhoto:
switch (state.currentStep) { stepWidget = const UploadPhotoStepPageView();
case PostcardStep.uploadPhoto: break;
stepWidget = const UploadPhotoStepPageView(); case PostcardStep.addFilter:
break; stepWidget = const AddFilterStepPageView();
case PostcardStep.addFilter: break;
stepWidget = const AddFilterStepPageView(); case PostcardStep.writeMessage:
break; stepWidget = const WriteMessageStepPageView();
case PostcardStep.writeMessage: break;
stepWidget = const WriteMessageStepPageView(); case PostcardStep.preview:
break; stepWidget = const PreviewPostcardStepPageView();
case PostcardStep.preview: break;
stepWidget = const PreviewPostcardStepPageView(); case PostcardStep.purchase:
break; stepWidget = const PostcardPurchaseFormPageView();
case PostcardStep.purchase: break;
stepWidget = const PostcardPurchaseFormPageView(); case PostcardStep.checkout:
break; stepWidget = const PostcardCheckoutPageView();
case PostcardStep.checkout: break;
stepWidget = const PostcardCheckoutPageView(); case PostcardStep.orderSuccess:
break; stepWidget = const OrderSuccessPageView();
case PostcardStep.orderSuccess: break;
stepWidget = const OrderSuccessPageView(); case PostcardStep.myOrders:
break; stepWidget = const MyOrdersPageView();
case PostcardStep.myOrders: break;
stepWidget = const MyOrdersPageView(); case PostcardStep.myOrderPostcardPreview:
break; stepWidget = const OrderPostcardPreviewPageView();
case PostcardStep.myOrderPostcardPreview: break;
stepWidget = const OrderPostcardPreviewPageView(); }
}
return Scaffold( return Scaffold(
backgroundColor: Colors.white, backgroundColor: Colors.white,
body: SafeArea(child: stepWidget), body: SafeArea(child: stepWidget),
); );
}, },
),
); );
} }
} }

View File

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

View File

@@ -8,13 +8,71 @@ import '../blocs/postcard_creation_bloc.dart';
import '../blocs/postcard_creation_events.dart'; import '../blocs/postcard_creation_events.dart';
import '../blocs/postcard_creation_state.dart'; import '../blocs/postcard_creation_state.dart';
class PostcardPurchaseFormPageView extends StatelessWidget { class PostcardPurchaseFormPageView extends StatefulWidget {
const PostcardPurchaseFormPageView({super.key}); 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<PostcardCreationBloc, PostcardCreationState>( return BlocBuilder<PostcardCreationBloc, PostcardCreationState>(
builder: (context, state) { builder: (context, state) {
final bloc = context.read<PostcardCreationBloc>(); final bloc = context.read<PostcardCreationBloc>();
return SafeArea( return SafeArea(
@@ -23,8 +81,7 @@ class PostcardPurchaseFormPageView extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
CommonAppBar(isWhiteLogo: false, isProfilePage: false, showDivider: true,), CommonAppBar(isWhiteLogo: false, isProfilePage: false, showDivider: true),
// Order ID // Order ID
Text( Text(
"#78895436", "#78895436",
@@ -36,7 +93,7 @@ class PostcardPurchaseFormPageView extends StatelessWidget {
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
// Postcard image + title // Title
Row( Row(
children: [ children: [
ClipRRect( ClipRRect(
@@ -58,24 +115,38 @@ class PostcardPurchaseFormPageView extends StatelessWidget {
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Expanded( Expanded(
child: TextField( child: Column(
decoration: InputDecoration( crossAxisAlignment: CrossAxisAlignment.start,
hintText: "Add title", children: [
hintStyle: GoogleFonts.poppins( TextField(
color: const Color(0xff999999), fontSize: 14.sp), controller: titleCtrl,
enabledBorder: const UnderlineInputBorder( onChanged: (_) {
borderSide: setState(() => titleError = null);
BorderSide(color: Color(0xffFDCDCE), width: 1), },
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( if (titleError != null)
borderSide: Padding(
BorderSide(color: Color(0xffFDCDCE), width: 1), padding: const EdgeInsets.only(top: 4),
), child: Text(titleError!,
), style: const TextStyle(
style: GoogleFonts.poppins(fontSize: 14.sp), color: Colors.red, fontSize: 12)),
onChanged: (val) { ),
// You can dispatch event here: bloc.add(UpdateTitle(val)); ],
},
), ),
), ),
], ],
@@ -83,7 +154,7 @@ class PostcardPurchaseFormPageView extends StatelessWidget {
const SizedBox(height: 28), const SizedBox(height: 28),
// Personal details section // Personal details
Text( Text(
"Add personal details", "Add personal details",
style: TextStyle( style: TextStyle(
@@ -97,21 +168,45 @@ class PostcardPurchaseFormPageView extends StatelessWidget {
_buildInputField( _buildInputField(
label: "Full Name", label: "Full Name",
hint: "Lorem Ipsum", hint: "Lorem Ipsum",
controller: fullNameCtrl,
errorText: fullNameError,
onChanged: (_) {
setState(() => fullNameError = null);
},
), ),
_buildInputField( _buildInputField(
label: "Email ID", label: "Email ID",
hint: "Lorem@gmail.com", hint: "Lorem@gmail.com",
icon: Icons.email_outlined, icon: Icons.email_outlined,
controller: emailCtrl,
errorText: emailError,
type: TextInputType.emailAddress,
onChanged: (_) {
setState(() => emailError = null);
},
), ),
_buildInputField( _buildInputField(
label: "Phone number", label: "Phone number",
hint: "+91 9999 999 999", hint: "+91 9999 999 999",
icon: Icons.phone_outlined, 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), const SizedBox(height: 28),
// Address details section
Text( Text(
"Add address details", "Add address details",
style: TextStyle( style: TextStyle(
@@ -122,10 +217,51 @@ class PostcardPurchaseFormPageView extends StatelessWidget {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
_buildInputField(label: "City", hint: "Lorem Ipsum"), _buildInputField(
_buildDropdownField(label: "Country", hint: "Lorem Ipsum"), label: "City",
_buildDropdownField(label: "State", hint: "Lorem Ipsum"), hint: "Lorem Ipsum",
_buildInputField(label: "Zip Code", hint: "000000"), 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), const SizedBox(height: 30),
@@ -134,7 +270,9 @@ class PostcardPurchaseFormPageView extends StatelessWidget {
width: double.infinity, width: double.infinity,
child: ElevatedButton( child: ElevatedButton(
onPressed: () { onPressed: () {
bloc.add(GoToNextStep()); if (validate()) {
bloc.add(GoToNextStep());
}
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xffF95F62), backgroundColor: const Color(0xffF95F62),
@@ -161,36 +299,39 @@ class PostcardPurchaseFormPageView extends StatelessWidget {
); );
} }
/// 🔹 Reusable text field widget /// TEXT FIELD (NO UI CHANGES)
Widget _buildInputField({ Widget _buildInputField({
required String label, required String label,
required String hint, required String hint,
required TextEditingController controller,
required Function(String) onChanged,
String? errorText,
IconData? icon, IconData? icon,
TextInputType? type
}) { }) {
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 18), padding: const EdgeInsets.only(bottom: 18),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(label,
label, style: GoogleFonts.poppins(
style: GoogleFonts.poppins( fontSize: 13.sp,
fontSize: 13.sp, fontWeight: FontWeight.w500,
fontWeight: FontWeight.w500, color: const Color(0xff1A1A1A))),
color: const Color(0xff1A1A1A),
),
),
const SizedBox(height: 6), const SizedBox(height: 6),
TextField( TextField(
controller: controller,
onChanged: onChanged,
keyboardType: type,
decoration: InputDecoration( decoration: InputDecoration(
hintText: hint, hintText: hint,
hintStyle: GoogleFonts.poppins( hintStyle: GoogleFonts.poppins(
color: const Color(0xff999999), color: const Color(0xff999999),
fontSize: 14.sp, fontSize: 14.sp,
), ),
suffixIcon: icon != null suffixIcon:
? Icon(icon, color: Colors.black, size: 20) icon != null ? Icon(icon, color: Colors.black, size: 20) : null,
: null,
contentPadding: contentPadding:
const EdgeInsets.symmetric(vertical: 14, horizontal: 12), const EdgeInsets.symmetric(vertical: 14, horizontal: 12),
enabledBorder: OutlineInputBorder( 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({ Widget _buildDropdownField({
required String label, required String label,
required String hint, required String hint,
required String? value,
required Function(String?) onChanged,
String? errorText,
}) { }) {
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 18), padding: const EdgeInsets.only(bottom: 18),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(label,
label, style: GoogleFonts.poppins(
style: GoogleFonts.poppins( fontSize: 13.sp,
fontSize: 13.sp, fontWeight: FontWeight.w500,
fontWeight: FontWeight.w500, color: const Color(0xff1A1A1A))),
color: const Color(0xff1A1A1A),
),
),
const SizedBox(height: 6), const SizedBox(height: 6),
DropdownButtonFormField<String>( DropdownButtonFormField<String>(
value: null, value: value,
decoration: InputDecoration( decoration: InputDecoration(
contentPadding: contentPadding:
const EdgeInsets.symmetric(vertical: 14, horizontal: 12), const EdgeInsets.symmetric(vertical: 14, horizontal: 12),
@@ -253,8 +400,14 @@ class PostcardPurchaseFormPageView extends StatelessWidget {
items: const [ items: const [
DropdownMenuItem(value: "Lorem Ipsum", child: Text("Lorem Ipsum")), 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 { class MessageCardWidget extends StatelessWidget {
final String message; final String message;
final String? selectedFont; final String? selectedFont;
const MessageCardWidget({super.key, required this.message, this.selectedFont}); MessageCardWidget({super.key, required this.message, this.selectedFont});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Stack( return Container(
alignment: Alignment.center, constraints: BoxConstraints(minHeight: 227.h),
children: [ padding: EdgeInsets.symmetric(horizontal: 8, vertical: 8.h),
Image.asset( decoration: BoxDecoration(
'assets/images/postcard_bg.png', borderRadius: BorderRadius.circular(5.r),
width: double.infinity, gradient: const LinearGradient(
fit: BoxFit.contain, 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( // ---------- IMPORTANT: IntrinsicHeight ensures children get bounded height ----------
right: 10, child: IntrinsicHeight(
top: 50, child: Row(
child: SizedBox( crossAxisAlignment: CrossAxisAlignment.stretch, // stretch so children fill height
width: 150.w, children: [
child: Text(message, // ---------------- LEFT SECTION ---------------- //
textAlign: TextAlign.left, Expanded(
style: TextStyle( child: Column(
fontFamily: selectedFont ?? crossAxisAlignment: CrossAxisAlignment.start,
GoogleFonts.poppins().fontFamily, // SpaceBetween ensures top content stays top and CityCards.co stays bottom
color: Colors.black, mainAxisAlignment: MainAxisAlignment.spaceBetween,
fontSize: 10, 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return SizedBox(
width: double.infinity, width: double.infinity,
height: 230.h, height: 227.h,
padding: const EdgeInsets.all(10), child: ClipRRect(
decoration: BoxDecoration( borderRadius: BorderRadius.circular(6.r),
gradient: LinearGradient(colors: [ child: Image.file(File(imagePath), fit: BoxFit.cover)),
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),
); );
} }
} }

View File

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