8 Commits

Author SHA1 Message Date
Vinayakkadge04
7249870aa9 test 2025-11-19 13:46:43 +05:30
vishal-kaklotar-wdi
7dbeef2f80 integrated share plus package 2025-11-19 13:13:16 +05:30
vishal-kaklotar-wdi
1ca940e5cf worked on bu fix 2025-11-19 11:26:23 +05:30
Vinayakkadge04
b54739953a Fixed bug on pass_card_view 2025-11-14 16:21:18 +05:30
Vinayakkadge04
323d730e2d Used url_launcher 2025-11-14 11:29:16 +05:30
Vinayakkadge04
f3c98df517 worked on launch_url for redirect call & email 2025-11-11 14:18:39 +05:30
Vinayakkadge04
0ef09633e0 Updated Splash Screen... 2025-11-11 14:18:39 +05:30
72aae68e2d vinayak's pull merged 2025-11-11 14:18:35 +05:30
52 changed files with 2116 additions and 1905 deletions

View File

@@ -1,33 +1,42 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<application
android:label="CityCard Customer"
android:name="${applicationName}"
android:icon="@mipmap/launcher_icon">
android:icon="@mipmap/launcher_icon"
android:label="CityCard Customer">
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/NormalTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:scheme="mailto" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
@@ -43,8 +52,8 @@
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
<action android:name="android.intent.action.PROCESS_TEXT" />
<data android:mimeType="text/plain" />
</intent>
</queries>
</manifest>

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

@@ -1,3 +0,0 @@
description: This file stores settings for Dart & Flutter DevTools.
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
extensions:

View File

@@ -1,12 +1,7 @@
PODS:
- Flutter (1.0.0)
- flutter_angle (0.3.8):
- Flutter
- FlutterAngle (~> 0.0.8)
- FlutterMacOS
- flutter_native_splash (2.4.3):
- Flutter
- FlutterAngle (0.0.8)
- geolocator_apple (1.2.0):
- Flutter
- FlutterMacOS
@@ -26,10 +21,12 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- share_plus (0.0.1):
- Flutter
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- three_js_sensors (0.1.2):
- url_launcher_ios (0.0.1):
- Flutter
- video_player_avfoundation (0.0.1):
- Flutter
@@ -37,28 +34,25 @@ PODS:
DEPENDENCIES:
- Flutter (from `Flutter`)
- flutter_angle (from `.symlinks/plugins/flutter_angle/darwin`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- geolocator_apple (from `.symlinks/plugins/geolocator_apple/darwin`)
- google_maps_flutter_ios (from `.symlinks/plugins/google_maps_flutter_ios/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- three_js_sensors (from `.symlinks/plugins/three_js_sensors/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
SPEC REPOS:
trunk:
- FlutterAngle
- Google-Maps-iOS-Utils
- GoogleMaps
EXTERNAL SOURCES:
Flutter:
:path: Flutter
flutter_angle:
:path: ".symlinks/plugins/flutter_angle/darwin"
flutter_native_splash:
:path: ".symlinks/plugins/flutter_native_splash/ios"
geolocator_apple:
@@ -71,18 +65,18 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
share_plus:
:path: ".symlinks/plugins/share_plus/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
three_js_sensors:
:path: ".symlinks/plugins/three_js_sensors/ios"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
video_player_avfoundation:
:path: ".symlinks/plugins/video_player_avfoundation/darwin"
SPEC CHECKSUMS:
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_angle: 7b1a2b3e733221bf2e0325e42fc3edf95b5d44c4
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
FlutterAngle: c810891af800750361b1d0e7cc944f2338d5ae18
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
Google-Maps-iOS-Utils: 0a484b05ed21d88c9f9ebbacb007956edd508a96
google_maps_flutter_ios: 0291eb2aa252298a769b04d075e4a9d747ff7264
@@ -90,8 +84,9 @@ SPEC CHECKSUMS:
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
three_js_sensors: f516b092803411e05b1e3dc7625efa36acd8f455
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
video_player_avfoundation: dd410b52df6d2466a42d28550e33e4146928280a
PODFILE CHECKSUM: 1857a7cdb7dfafe45f2b0e9a9af44644190f7506

View File

@@ -23,7 +23,7 @@
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>3</string>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSCameraUsageDescription</key>

View File

@@ -1,484 +0,0 @@
import 'package:citycards_customer/attraction_details/share_bottomsheet.dart';
import 'package:citycards_customer/common_packages/app_bar.dart';
import 'package:citycards_customer/common_packages/custom_text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../core/route_constants.dart';
class AttractionDetailsView extends StatelessWidget {
const AttractionDetailsView({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
Image.asset(
'assets/images/koh_rong_samloem_banner.png',
height: 377.h,
width: double.infinity,
fit: BoxFit.cover,
),
Positioned(
top: 0,
left: 0,
right: 0,
child: SafeArea(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 10.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CommonAppBar(isWhiteLogo: true, isProfilePage: false, showDivider: true,),
SizedBox(height: 10.h),
Row(
children: [
GestureDetector(
onTap: () => Navigator.pop(context),
child: Icon(
Icons.arrow_back,
size: 24.sp,
color: Colors.white,
),
),
SizedBox(width: 8.w),
Text(
"Koh Rong Samloem",
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
],
),
],
),
),
),
),
Positioned(
bottom: 31.h,
left: 12.w,
child: Text(
"Koh Rong\nSamloem",
style: TextStyle(
color: Colors.white,
fontSize: 44.sp,
fontWeight: FontWeight.w500,
height: 1.2,
),
),
),
Positioned(
bottom: 31.h,
right: 17.w,
child: GestureDetector(
onTap: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) => const ShareBottomSheet(),
);
},
child: Container(
height: 36.h,
width: 36.w,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20.r),
),
child: Center(
child: Icon(
Icons.share_sharp,
color: Colors.black,
size: 18.sp,
),
),
),
),
),
],
),
// About Section
Padding(
padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 30.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"About",
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w400,
),
),
SizedBox(height: 12.32.h),
Text(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Convallis condimentum morbi non egestas enim amet sagittis. Proin sed aliquet rhoncus ut pellentesque ullamcorper sit eget ac.Sit nisi, cras amet varius eget egestas pellentesque. Cursus gravida euismod non...",
style: TextStyle(
color: Color(0xFF262626),
fontWeight: FontWeight.w400,
fontSize: 14.sp,
height: 1.5,
),
),
],
),
),
SizedBox(height: 41.h),
// 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),
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),
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: [
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",
),
],
),
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...",
),
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: 24.h),
],
),
),
),
);
}
Widget includedBox(String icon, String title, String disc) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 10.h),
decoration: BoxDecoration(
color: Color(0xFFFFF5F5),
borderRadius: BorderRadius.circular(10.r),
border: Border.all(color: Color(0xFFFDCDCE)),
),
child: IntrinsicWidth(
child: Row(
children: [
Image.asset(icon, scale: 4),
SizedBox(width: 16.w),
Column(
children: [
CustomText(
text: title,
size: 16.sp,
weight: FontWeight.w500,
color: Color(0xFF212121),
),
SizedBox(height: 4.h),
CustomText(
text: disc,
size: 11.sp,
weight: FontWeight.w400,
color: Color(0xFF666666),
),
],
),
],
),
),
);
}
Widget faqBox(String title, String desc) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 12.h),
decoration: BoxDecoration(
color: Color(0xFFFFF5F5),
border: Border.all(color: Color(0xFFFDCDCE)),
borderRadius: BorderRadius.circular(10.r),
),
child: Column(
children: [
Row(
children: [
CustomText(
text: title,
size: 16.sp,
weight: FontWeight.w500,
color: Color(0xFF212121),
),
SizedBox(width: 20.w),
Icon(Icons.arrow_forward_ios_outlined, size: 18.sp),
],
),
SizedBox(height: 9.h),
CustomText(text: desc, size: 11.sp, color: Color(0xFF7D7D7D)),
],
),
);
}
}

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

@@ -0,0 +1,555 @@
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 'package:share_plus/share_plus.dart';
import '../../core/route_constants.dart';
class AttractionDetailsView extends StatelessWidget {
AttractionDetailsView({super.key});
final AttractionDetailsViewModel viewModel = AttractionDetailsViewModel();
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
Image.asset(
'assets/images/koh_rong_samloem_banner.png',
height: 377.h,
width: double.infinity,
fit: BoxFit.cover,
),
Positioned(
top: 0,
left: 0,
right: 0,
child: SafeArea(
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: 20.w,
vertical: 10.h,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CommonAppBar(
isWhiteLogo: true,
isProfilePage: false,
showDivider: true,
),
SizedBox(height: 10.h),
Row(
children: [
GestureDetector(
onTap: () => Navigator.pop(context),
child: Icon(
Icons.arrow_back,
size: 24.sp,
color: Colors.white,
),
),
SizedBox(width: 8.w),
Text(
"Koh Rong Samloem",
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
],
),
],
),
),
),
),
Positioned(
bottom: 31.h,
left: 12.w,
child: Text(
"Koh Rong\nSamloem",
style: TextStyle(
color: Colors.white,
fontSize: 44.sp,
fontWeight: FontWeight.w500,
height: 1.2,
),
),
),
Positioned(
bottom: 31.h,
right: 17.w,
child: GestureDetector(
onTap: () {
Share.share(
'Check out my City Card app!',
subject: 'City Card App',
);
// showModalBottomSheet(
// context: context,
// isScrollControlled: true,
// backgroundColor: Colors.transparent,
// builder: (context) => const ShareBottomSheet(),
// );
},
child: Container(
height: 36.h,
width: 36.w,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20.r),
),
child: Center(
child: Icon(
Icons.share_sharp,
color: Colors.black,
size: 18.sp,
),
),
),
),
),
],
),
// About Section
Padding(
padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 30.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"About",
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w400,
),
),
SizedBox(height: 12.32.h),
Text(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Convallis condimentum morbi non egestas enim amet sagittis. Proin sed aliquet rhoncus ut pellentesque ullamcorper sit eget ac.Sit nisi, cras amet varius eget egestas pellentesque. Cursus gravida euismod non...",
style: TextStyle(
color: Color(0xFF262626),
fontWeight: FontWeight.w400,
fontSize: 14.sp,
height: 1.5,
),
),
],
),
),
SizedBox(height: 41.h),
// Booking Section
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: BlocBuilder<AttractionDetailsBloc, AttractionDetailsState>(
builder: (context, state) {
print("fajfasfasjfjas======= ${state.fromByPass}");
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
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,
),
),
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",
),
],
),
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: 24.h),
],
),
),
),
);
}
Widget includedBox(String icon, String title, String disc) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 10.h),
decoration: BoxDecoration(
color: Color(0xFFFFF5F5),
borderRadius: BorderRadius.circular(10.r),
border: Border.all(color: Color(0xFFFDCDCE)),
),
child: IntrinsicWidth(
child: Row(
children: [
Image.asset(icon, scale: 4),
SizedBox(width: 16.w),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CustomText(
text: title,
size: 16.sp,
weight: FontWeight.w500,
color: Color(0xFF212121),
),
SizedBox(height: 4.h),
CustomText(
text: disc,
size: 11.sp,
weight: FontWeight.w400,
color: Color(0xFF666666),
),
],
),
],
),
),
);
}
Widget faqBox(String title, String desc, Function() onTap) {
return GestureDetector(
onTap: onTap,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 12.h),
decoration: BoxDecoration(
color: Color(0xFFFFF5F5),
border: Border.all(color: Color(0xFFFDCDCE)),
borderRadius: BorderRadius.circular(10.r),
),
child: Column(
children: [
Row(
children: [
CustomText(
text: title,
size: 16.sp,
weight: FontWeight.w500,
color: Color(0xFF212121),
),
SizedBox(width: 20.w),
Icon(Icons.arrow_forward_ios_outlined, size: 18.sp),
],
),
SizedBox(height: 9.h),
CustomText(text: desc, size: 11.sp, color: Color(0xFF7D7D7D)),
],
),
),
);
}
}

View File

@@ -0,0 +1,33 @@
import 'package:url_launcher/url_launcher.dart';
import 'package:flutter/material.dart';
class AttractionDetailsViewModel {
// 📞 Call method
Future<void> makePhoneCall(String phoneNumber, BuildContext context) async {
final Uri url = Uri(scheme: 'tel', path: phoneNumber);
if (await canLaunchUrl(url)) {
await launchUrl(url);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Could not launch $phoneNumber')),
);
}
}
// 📧 Email method
Future<void> sendEmail(String emailAddress, BuildContext context) async {
final Uri url = Uri(
scheme: 'mailto',
path: emailAddress,
query: 'subject=Hello City Cards&body=Hi there,',
);
if (await canLaunchUrl(url)) {
await launchUrl(url);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Could not launch mail app')),
);
}
}
}

View File

@@ -1,5 +1,3 @@
import 'package:citycards_customer/common_packages/common_app_texts.dart';
import '../models/attraction_model.dart';
class AttractionsRepository {
@@ -10,7 +8,7 @@ class AttractionsRepository {
location: "Krong Siem Reap",
price: "\$25",
image: "assets/dummy/dummy_1.jpg",
tags: ["Unlimited Card", "${CommonAppText.selectiveCard} Card"],
tags: ["Unlimited Card", "Flexi Card"],
isBookingRequired: false,
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Convallis condimentum morbi non egestas enim amet sagittis. Proin sed aliquet rhoncus ut pellentesque ullamcorper sit eget ac.Sit nisi, cras amet varius eget egestas pellentesque. Cursus gravida euismod non... ",
@@ -30,7 +28,7 @@ class AttractionsRepository {
location: "Krong Siem Reap",
price: "\$25",
image: "assets/dummy/dummy_3.jpg",
tags: ["Unlimited Card", "${CommonAppText.selectiveCard} Card"],
tags: ["Unlimited Card", "Flexi Card"],
isBookingRequired: false,
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Convallis condimentum morbi non egestas enim amet sagittis. Proin sed aliquet rhoncus ut pellentesque ullamcorper sit eget ac.Sit nisi, cras amet varius eget egestas pellentesque. Cursus gravida euismod non... ",
@@ -40,7 +38,7 @@ class AttractionsRepository {
location: "Krong Siem Reap",
price: "\$25",
image: "assets/dummy/dummy_4.jpg",
tags: ["${CommonAppText.selectiveCard} Card"],
tags: ["Flexi Card"],
isBookingRequired: false,
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Convallis condimentum morbi non egestas enim amet sagittis. Proin sed aliquet rhoncus ut pellentesque ullamcorper sit eget ac.Sit nisi, cras amet varius eget egestas pellentesque. Cursus gravida euismod non... ",
@@ -50,7 +48,7 @@ class AttractionsRepository {
location: "Krong Siem Reap",
price: "\$25",
image: "assets/dummy/dummy_5.jpg",
tags: ["Unlimited Card", "${CommonAppText.selectiveCard} Card"],
tags: ["Unlimited Card", "Flexi Card"],
isBookingRequired: false,
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Convallis condimentum morbi non egestas enim amet sagittis. Proin sed aliquet rhoncus ut pellentesque ullamcorper sit eget ac.Sit nisi, cras amet varius eget egestas pellentesque. Cursus gravida euismod non... ",
@@ -65,7 +63,7 @@ class AttractionsRepository {
location: "Krong Siem Reap",
price: "\$25",
image: "assets/dummy/dummy_1.jpg",
tags: ["Unlimited Card", "${CommonAppText.selectiveCard} Card"],
tags: ["Unlimited Card", "Flexi Card"],
isBookingRequired: true,
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Convallis condimentum morbi non egestas enim amet sagittis. Proin sed aliquet rhoncus ut pellentesque ullamcorper sit eget ac.Sit nisi, cras amet varius eget egestas pellentesque. Cursus gravida euismod non... ",
@@ -85,7 +83,7 @@ class AttractionsRepository {
location: "Krong Siem Reap",
price: "\$25",
image: "assets/dummy/dummy_3.jpg",
tags: ["Unlimited Card", "${CommonAppText.selectiveCard} Card"],
tags: ["Unlimited Card", "Flexi Card"],
isBookingRequired: true,
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Convallis condimentum morbi non egestas enim amet sagittis. Proin sed aliquet rhoncus ut pellentesque ullamcorper sit eget ac.Sit nisi, cras amet varius eget egestas pellentesque. Cursus gravida euismod non... ",
@@ -95,7 +93,7 @@ class AttractionsRepository {
location: "Krong Siem Reap",
price: "\$25",
image: "assets/dummy/dummy_4.jpg",
tags: ["${CommonAppText.selectiveCard} Card"],
tags: ["Flexi Card"],
isBookingRequired: true,
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Convallis condimentum morbi non egestas enim amet sagittis. Proin sed aliquet rhoncus ut pellentesque ullamcorper sit eget ac.Sit nisi, cras amet varius eget egestas pellentesque. Cursus gravida euismod non... ",
@@ -105,7 +103,7 @@ class AttractionsRepository {
location: "Krong Siem Reap",
price: "\$25",
image: "assets/dummy/dummy_5.jpg",
tags: ["Unlimited Card", "${CommonAppText.selectiveCard} Card"],
tags: ["Unlimited Card", "Flexi Card"],
isBookingRequired: true,
description:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Convallis condimentum morbi non egestas enim amet sagittis. Proin sed aliquet rhoncus ut pellentesque ullamcorper sit eget ac.Sit nisi, cras amet varius eget egestas pellentesque. Cursus gravida euismod non... ",

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,19 +18,27 @@ class AttractionsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) {
final bloc = AttractionsBloc(AttractionsRepository());
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());
}
// 🔥 Trigger event based on source
if (source == "home") {
bloc.add(LoadAttractions());
} else if (source == "qrPass") {
bloc.add(LoadMyPassAttraction());
}
return bloc;
},
),
BlocProvider(create: (_) => AttractionDetailsBloc(),
return bloc;
},
)
],
child: BlocBuilder<AttractionsBloc, AttractionsState>(
builder: (context, state) {
final bloc = context.read<AttractionsBloc>();

View File

@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../common_packages/common_app_texts.dart';
import '../../core/route_constants.dart';
import '../models/attraction_model.dart';
@@ -12,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),
@@ -87,13 +91,13 @@ class AttractionCard extends StatelessWidget {
vertical: 4,
),
decoration: BoxDecoration(
color: tag == "${CommonAppText.selectiveCard} Card"
color: tag == "Flexi Card"
? const Color(0xffF95FAF).withOpacity(0.1)
: const Color(
0xffF95F62,
).withOpacity(0.1),
border: Border.all(
color: tag == "${CommonAppText.selectiveCard} Card"
color: tag == "Flexi Card"
? const Color(0xffF95FAF)
: const Color(0xffF95F62),
),

View File

@@ -7,8 +7,6 @@ import 'package:citycards_customer/core/route_constants.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../common_packages/common_app_texts.dart';
class BuyPassView extends StatelessWidget {
BuyPassView({super.key});
@@ -70,9 +68,9 @@ class BuyPassView extends StatelessWidget {
scrollDirection: Axis.horizontal,
child: Row(
children: [
PassCardView(themeColor: Color(0xFFF97316)),
PassCardView(themeColor: Color(0xFFF95FAF)),
SizedBox(width: 12.w),
PassCardView(themeColor: Color(0xFF1E8AF6),),
PassCardView(themeColor: Color(0xFF1E8AF6)),
],
),
),
@@ -205,8 +203,6 @@ class BuyPassView extends StatelessWidget {
text: offer["description"] ?? "",
color: Colors.black.withOpacity(.6),
size: 12.sp,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
@@ -219,7 +215,7 @@ class BuyPassView extends StatelessWidget {
Center(
child: PaymentCard(
city: 'Melbourne',
tag: '${CommonAppText.selectiveCard} Card',
tag: 'Flexi Card',
oldPrice: 120,
newPrice: 90,
),

View File

@@ -1,8 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../common_packages/common_app_texts.dart';
class FeatureTable extends StatelessWidget {
const FeatureTable({super.key});
@@ -68,7 +66,7 @@ class FeatureTable extends StatelessWidget {
padding: EdgeInsets.symmetric(vertical: 6.h),
child: Center(
child: Text(
'${CommonAppText.selectiveCard}',
'Flexi',
style: TextStyle(fontWeight: FontWeight.w500, fontSize: 16.sp),
),
),

View File

@@ -2,8 +2,6 @@ import 'package:citycards_customer/common_packages/custom_text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../common_packages/common_app_texts.dart';
class PassCardView extends StatelessWidget {
final Color? themeColor;
final String? city;
@@ -28,7 +26,7 @@ class PassCardView extends StatelessWidget {
border: Border.all(color:( themeColor ?? Color(0xFFF95FAF)).withOpacity(0.24)),
borderRadius: BorderRadius.circular(8.r),
),
child: Row(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
@@ -145,7 +143,7 @@ class PassCardView extends StatelessWidget {
text: TextSpan(
children: [
TextSpan(
text: "${CommonAppText.selectiveCard} ",
text: "Flexi ",
style: TextStyle(color: Colors.white, fontSize: 16.sp),
),
TextSpan(
@@ -160,6 +158,6 @@ class PassCardView extends StatelessWidget {
),
],
),
);
);
}
}

View File

@@ -6,8 +6,6 @@ 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 '../../checkout/widget/login_email_bottomsheet.dart';
import '../../common_packages/common_app_texts.dart';
import '../blocs/pass_bloc.dart';
class MyPassesPage extends StatelessWidget {
@@ -25,13 +23,14 @@ class MyPassesPage extends StatelessWidget {
children: [
SizedBox(height: 22.h),
Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(
color: Color(0xFFF95FAF).withOpacity(0.2),
),
borderRadius: BorderRadius.circular(8.r),
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: [
@@ -155,7 +154,7 @@ class MyPassesPage extends StatelessWidget {
width: 35.w,
height: 123.h,
decoration: BoxDecoration(
color: Color(0xFFF97316),
color: Color(0xFFF95FAF),
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(8.r),
topRight: Radius.circular(8.r),
@@ -168,7 +167,7 @@ class MyPassesPage extends StatelessWidget {
text: TextSpan(
children: [
TextSpan(
text: "${CommonAppText.selectiveCard} ",
text: "Flexi ",
style: TextStyle(
color: Colors.white,
fontSize: 16.sp,
@@ -190,6 +189,8 @@ class MyPassesPage extends StatelessWidget {
],
),
),
),
SizedBox(height: 15.h),
Container(
padding: EdgeInsets.symmetric(
@@ -335,19 +336,7 @@ class MyPassesPage extends StatelessWidget {
SizedBox(height: 150.h,),
CustomFilledButton(
onTap: () {
showModalBottomSheet(
backgroundColor: Colors.white,
context: context,
isScrollControlled: true,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(12.r),
),
),
builder: (_) => const LoginEmailBottomsheet(),
);
},
onTap: () {},
width: double.infinity,
label: "Proceed to Checkout",
),

View File

@@ -5,7 +5,6 @@ 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 '../../checkout/widget/login_email_bottomsheet.dart';
import '../blocs/postcard_bloc.dart';
class MyPostCardsPage extends StatelessWidget {
@@ -157,19 +156,7 @@ class MyPostCardsPage extends StatelessWidget {
),
SizedBox(height: 60.h),
CustomFilledButton(
onTap: () {
showModalBottomSheet(
backgroundColor: Colors.white,
context: context,
isScrollControlled: true,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(12.r),
),
),
builder: (_) => const LoginEmailBottomsheet(),
);
},
onTap: () {},
width: double.infinity,
label: "Proceed to Checkout",
),

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,365 +7,391 @@ 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';
import '../../common_packages/common_app_texts.dart';
class CheckoutView extends StatelessWidget {
const CheckoutView({super.key});
@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),
],
),
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),
],
),
SizedBox(height: 22.h),
Expanded(
child: Container(
SizedBox(height: 22.h),
Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Color(0xFFF95FAF).withOpacity(0.2)),
border: Border.all(
color: Color(0xFFF95FAF).withOpacity(0.2),
),
borderRadius: BorderRadius.circular(8.r),
),
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,
child: Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(8.r),
bottomLeft: Radius.circular(8.r),
),
SizedBox(height: 5.h),
CustomText(
text: "2 Days",
color: Color(0xFF8E8E8E),
size: 12.sp,
child: Image.asset(
"assets/images/card_banner.png",
scale: 4,
width: 105.w,
height: 123.h,
fit: BoxFit.cover,
),
SizedBox(height: 5.h),
),
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(
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(0xFFF97316),
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: "${CommonAppText.selectiveCard} ",
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: 10.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) {
@@ -19,47 +42,81 @@ class LoginEmailBottomsheet extends StatelessWidget {
right: 20.h,
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: SafeArea(
child: SingleChildScrollView(
child: Column(
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: "Get Started", size: 18.sp, weight: FontWeight.w500),
SizedBox(height: 42.h),
CustomText(
text: "Enter your email to begin your CityCards journey",
size: 14.sp,
color: const Color(0xFF000000).withOpacity(.6),
),
SizedBox(height: 12.h),
TextField(
decoration: InputDecoration(
filled: true,
contentPadding: EdgeInsets.symmetric(vertical: 6.h),
fillColor: const Color(0xFFFFF5F5),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(color: const Color(0xFFBB474A), width: 0.4.w),
borderRadius: BorderRadius.circular(8.sp),
child: SingleChildScrollView(
child: Column(
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: "Get Started",
size: 18.sp,
weight: FontWeight.w500,
),
SizedBox(height: 42.h),
CustomText(
text: "Enter your email to begin your CityCards journey",
size: 14.sp,
color: const Color(0xFF000000).withOpacity(.6),
),
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,
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: const Color(0xFFBB474A), width: 0.4.w),
borderRadius: BorderRadius.circular(8.sp),
borderRadius: BorderRadius.circular(8.sp),
),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(
color: const Color(0xFFBB474A),
width: 0.4.w,
),
prefixIcon: const Icon(Icons.email_outlined, color: Color(0xFFF95F62)),
hintText: "john.doe@gmail.com",
hintStyle: TextStyle(
color: const Color(0xFF000000).withOpacity(0.6),
fontSize: 12.sp,
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,
),
borderRadius: BorderRadius.circular(8.sp),
),
prefixIcon: const Icon(
Icons.email_outlined,
color: Color(0xFFF95F62),
),
hintText: "john.doe@gmail.com",
hintStyle: TextStyle(
color: const Color(0xFF000000).withOpacity(0.6),
fontSize: 12.sp,
),
),
SizedBox(height: 38.h),
CustomFilledButton(
onTap: () {
),
SizedBox(height: 38.h),
CustomFilledButton(
onTap: () {
if (emailValidate()) {
Navigator.pop(context);
showModalBottomSheet(
context: context,
@@ -70,44 +127,47 @@ class LoginEmailBottomsheet extends StatelessWidget {
top: Radius.circular(12.r),
),
),
builder: (_) => VerifyOtpBottomsheet(),
builder: (_) => BlocProvider(
create: (rootcontext) => EmailVerifyBloc(),
child: VerifyOtpBottomsheet(),
),
);
},
label: "Continue",
width: double.infinity,
),
SizedBox(height: 20.h),
InkWell(
onTap: (){
Navigator.of(context).pushNamed(RouteConstants.createAcct);
},
child: Text.rich(
TextSpan(
children: [
TextSpan(
text: "Already have an account?",
style: TextStyle(
color: Colors.black.withOpacity(0.6),
fontSize: 12.sp,
fontWeight: FontWeight.w400,
),
}
},
label: "Continue",
width: double.infinity,
),
SizedBox(height: 20.h),
InkWell(
onTap: () {
Navigator.of(context).pushNamed(RouteConstants.createAcct);
},
child: Text.rich(
TextSpan(
children: [
TextSpan(
text: "Already have an account?",
style: TextStyle(
color: Colors.black.withOpacity(0.6),
fontSize: 12.sp,
fontWeight: FontWeight.w400,
),
TextSpan(
text: " Sign in",
style: TextStyle(
color: const Color(0xFFF95F62),
fontSize: 12.sp,
fontWeight: FontWeight.w600,
),
),
TextSpan(
text: " Sign in",
style: TextStyle(
color: const Color(0xFFF95F62),
fontSize: 12.sp,
fontWeight: FontWeight.w600,
),
],
),
),
],
),
),
SizedBox(height: 15.h),
],
),
),
SizedBox(height: 15.h),
],
),
),
);

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

@@ -1,3 +0,0 @@
class CommonAppText {
static const String selectiveCard = "Selective";
}

View File

@@ -1,6 +1,6 @@
import 'package:citycards_customer/Profile/profile_page_view.dart';
import 'package:citycards_customer/add_details/add_details_view.dart';
import 'package:citycards_customer/attraction_details/attraction_details_view.dart';
import 'package:citycards_customer/attraction_details/view/attraction_details_view.dart';
import 'package:citycards_customer/buy_a_pass/view/buy_pass_view.dart';
import 'package:citycards_customer/checkout/view/checkout_view.dart';
import 'package:citycards_customer/common_bloc/language_selection_bloc.dart';
@@ -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

@@ -5,7 +5,7 @@ import 'package:citycards_customer/postcard/views/add_filter_step_page_view.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../attraction_details/attraction_details_view.dart';
import '../attraction_details/view/attraction_details_view.dart';
import '../attractions/views/attractions_page_view.dart';
import '../buy_a_pass/view/buy_pass_view.dart';
import '../checkout/view/checkout_view.dart';
@@ -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

@@ -17,6 +17,7 @@ class CreateAccountView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
@@ -109,7 +110,7 @@ class CreateAccountView extends StatelessWidget {
),
),
SizedBox(height: 16.h),
SizedBox(height: 36.h),
CustomFilledButton(
width: double.infinity,
onTap: (){}, label: "Create Account")

View File

@@ -1,112 +0,0 @@
class CityList {
List<Cities>? cities;
List<UpcomingCities>? upcomingCities;
CityList({this.cities, this.upcomingCities});
CityList.fromJson(Map<String, dynamic> json) {
if (json['cities'] != null) {
cities = <Cities>[];
json['cities'].forEach((v) {
cities!.add(new Cities.fromJson(v));
});
}
if (json['upcomingCities'] != null) {
upcomingCities = <UpcomingCities>[];
json['upcomingCities'].forEach((v) {
upcomingCities!.add(new UpcomingCities.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
if (this.cities != null) {
data['cities'] = this.cities!.map((v) => v.toJson()).toList();
}
if (this.upcomingCities != null) {
data['upcomingCities'] =
this.upcomingCities!.map((v) => v.toJson()).toList();
}
return data;
}
}
class Cities {
int? id;
String? cityName;
String? tagLine;
String? bannerImage;
int? indivisualTicketAmt;
int? cityCardTicketAmt;
int? saveAmount;
String? saveLabel;
List<UpcomingCities>? upcomingCities;
Cities(
{this.id,
this.cityName,
this.tagLine,
this.bannerImage,
this.indivisualTicketAmt,
this.cityCardTicketAmt,
this.saveAmount,
this.saveLabel,
this.upcomingCities});
Cities.fromJson(Map<String, dynamic> json) {
id = json['id'];
cityName = json['cityName'];
tagLine = json['tagLine'];
bannerImage = json['bannerImage'];
indivisualTicketAmt = json['indivisualTicketAmt'];
cityCardTicketAmt = json['cityCardTicketAmt'];
saveAmount = json['saveAmount'];
saveLabel = json['saveLabel'];
if (json['upcomingCities'] != null) {
upcomingCities = <UpcomingCities>[];
json['upcomingCities'].forEach((v) {
upcomingCities!.add(new UpcomingCities.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['cityName'] = this.cityName;
data['tagLine'] = this.tagLine;
data['bannerImage'] = this.bannerImage;
data['indivisualTicketAmt'] = this.indivisualTicketAmt;
data['cityCardTicketAmt'] = this.cityCardTicketAmt;
data['saveAmount'] = this.saveAmount;
data['saveLabel'] = this.saveLabel;
if (this.upcomingCities != null) {
data['upcomingCities'] =
this.upcomingCities!.map((v) => v.toJson()).toList();
}
return data;
}
}
class UpcomingCities {
int? id;
String? cityName;
String? imgPathName;
UpcomingCities({this.id, this.cityName, this.imgPathName});
UpcomingCities.fromJson(Map<String, dynamic> json) {
id = json['id'];
cityName = json['cityName'];
imgPathName = json['imgPathName'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['cityName'] = this.cityName;
data['imgPathName'] = this.imgPathName;
return data;
}
}

View File

@@ -23,11 +23,6 @@ class RegisteredUserHomePage extends StatefulWidget {
class _RegisteredUserHomePageState extends State<RegisteredUserHomePage> {
final List<Map<String, String>> attractions = [
{
'title': 'Koh Rong Samloemr',
'subtitle': 'Lorem ipsum dolor sit amet...',
'image': 'assets/images/koh_rong.png',
},
{
'title': 'Long-Tail Boat Charter',
'subtitle': 'Lorem ipsum dolor sit amet...',
@@ -43,6 +38,11 @@ class _RegisteredUserHomePageState extends State<RegisteredUserHomePage> {
'subtitle': 'Lorem ipsum dolor sit amet...',
'image': 'assets/images/clock.png',
},
{
'title': 'Koh Rong Samloemr',
'subtitle': 'Lorem ipsum dolor sit amet...',
'image': 'assets/images/koh_rong.png',
},
];
@override

View File

@@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../core/route_constants.dart';
class AttractionsListView extends StatefulWidget {
final List<Map<String, String>> attractions;
@@ -39,74 +38,69 @@ class _AttractionsListViewState extends State<AttractionsListView> {
@override
Widget build(BuildContext context) {
return InkWell(
onTap: (){
Navigator.of(context).pushNamed(RouteConstants.attractionDetails);
},
child: Column(
children: [
SizedBox(
height: 240,
child: ListView.builder(
controller: _scrollController,
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.only(right: 16),
itemCount: widget.attractions.length,
itemBuilder: (context, index) {
final item = widget.attractions[index];
return Container(
alignment: Alignment.center,
margin: const EdgeInsets.only(right: 16),
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
border: Border.all(
color: const Color(0xFFF95F62).withOpacity(0.24),
),
borderRadius: BorderRadius.circular(16),
return Column(
children: [
SizedBox(
height: 240,
child: ListView.builder(
controller: _scrollController,
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.only(right: 16),
itemCount: widget.attractions.length,
itemBuilder: (context, index) {
final item = widget.attractions[index];
return Container(
alignment: Alignment.center,
margin: const EdgeInsets.only(right: 16),
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
border: Border.all(
color: const Color(0xFFF95F62).withOpacity(0.24),
),
child: Container(
height: 232,
width: 161,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
image: DecorationImage(
image: AssetImage(item['image']!),
fit: BoxFit.cover,
),
),
alignment: Alignment.bottomLeft,
padding: const EdgeInsets.all(12),
child: Text(
item['title']!,
style: GoogleFonts.poppins(
color: Colors.white,
fontWeight: FontWeight.w600,
fontSize: 14,
),
),
),
);
},
),
),
const SizedBox(height: 20),
Align(
alignment: Alignment.center,
child: SizedBox(
width: 200,
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: LinearProgressIndicator(
value: _scrollProgress,
minHeight: 6,
backgroundColor: const Color(0xffFEE7E7),
color: const Color(0xffF95F62),
borderRadius: BorderRadius.circular(16),
),
child: Container(
height: 232,
width: 161,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
image: DecorationImage(
image: AssetImage(item['image']!),
fit: BoxFit.cover,
),
),
alignment: Alignment.bottomLeft,
padding: const EdgeInsets.all(12),
child: Text(
item['title']!,
style: GoogleFonts.poppins(
color: Colors.white,
fontWeight: FontWeight.w600,
fontSize: 14,
),
),
),
);
},
),
),
const SizedBox(height: 20),
Align(
alignment: Alignment.center,
child: SizedBox(
width: 200,
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: LinearProgressIndicator(
value: _scrollProgress,
minHeight: 6,
backgroundColor: const Color(0xffFEE7E7),
color: const Color(0xffF95F62),
),
),
),
],
),
),
],
);
}
}

View File

@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../core/route_constants.dart';
@@ -11,14 +10,15 @@ class GetYourPassCard extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
width: double.infinity,
margin: EdgeInsets.symmetric(horizontal: 14.w, vertical: 0),
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 20.h),
margin: const EdgeInsets.symmetric(horizontal: 14, vertical: 0),
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 20),
decoration: BoxDecoration(
color: const Color(0xFFFFF1F1),
borderRadius: BorderRadius.circular(40.r),
border: Border.all(color: const Color(0xffFDCDCE))
color: const Color(0xFFFFF1F1),
borderRadius: BorderRadius.circular(40),
border: Border.all(color: Color(0xffFDCDCE))
),
child: Column(
children: [
// ===== Left Section =====
Row(
@@ -27,7 +27,7 @@ class GetYourPassCard extends StatelessWidget {
Text(
"Get your Pass",
style: GoogleFonts.poppins(
fontSize: 18.sp,
fontSize: 18,
fontWeight: FontWeight.w500,
color: Colors.black,
),
@@ -37,38 +37,37 @@ class GetYourPassCard extends StatelessWidget {
Navigator.of(context).pushNamed(RouteConstants.buyPass);
},
child: Container(
padding: EdgeInsets.all(8.r),
padding: const EdgeInsets.all(8),
decoration: const BoxDecoration(
color: Color(0xffF95F62),
shape: BoxShape.circle,
),
child: Icon(
child: const Icon(
Icons.arrow_forward,
color: Colors.black,
size: 18.sp,
size: 18,
),
),
),
],
),
SizedBox(height: 7.h),
const SizedBox(
height: 7,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
AttractionsAvatarStack(
imagePath: 'assets/images/get_your_pass_bg.jpg',
),
SizedBox(width: 8.w),
AttractionsAvatarStack(imagePath: 'assets/images/get_your_pass_bg.jpg',),
const SizedBox(width: 8),
Text(
"Attractions",
style: GoogleFonts.poppins(
fontSize: 13.sp,
color: Colors.black,
fontWeight: FontWeight.w400
fontSize: 13,
color: Colors.black,
fontWeight: FontWeight.w400
),
),
],
@@ -79,7 +78,7 @@ class GetYourPassCard extends StatelessWidget {
Text(
"From",
style: GoogleFonts.poppins(
fontSize: 12.sp,
fontSize: 12,
color: Colors.black87,
),
),
@@ -89,7 +88,7 @@ class GetYourPassCard extends StatelessWidget {
TextSpan(
text: "\$20",
style: GoogleFonts.poppins(
fontSize: 14.sp,
fontSize: 14,
fontWeight: FontWeight.w700,
color: Colors.black,
),
@@ -97,7 +96,7 @@ class GetYourPassCard extends StatelessWidget {
TextSpan(
text: " /Adult",
style: GoogleFonts.poppins(
fontSize: 13.sp,
fontSize: 13,
color: Colors.black87,
),
),
@@ -117,10 +116,10 @@ class GetYourPassCard extends StatelessWidget {
class AttractionsAvatarStack extends StatelessWidget {
const AttractionsAvatarStack({
super.key,
required this.imagePath,
this.size = 35,
this.count = 4,
this.overlap = 8,
required this.imagePath, // from your assets/figma
this.size = 35, // circle diameter
this.count = 4, // total circles including the last “16+”
this.overlap = 8, // how much they overlap
this.moreText = '16+',
});
@@ -132,13 +131,10 @@ class AttractionsAvatarStack extends StatelessWidget {
@override
Widget build(BuildContext context) {
final responsiveSize = size.r;
final responsiveOverlap = overlap.r;
final step = responsiveSize - responsiveOverlap;
final step = size - overlap; // horizontal step between circles
return SizedBox(
width: responsiveSize + (count - 1) * step,
height: responsiveSize,
width: size + (count - 1) * step,
height: size,
child: Stack(
clipBehavior: Clip.none,
children: List.generate(count, (i) {
@@ -148,7 +144,7 @@ class AttractionsAvatarStack extends StatelessWidget {
return Positioned(
left: left,
child: _AvatarCircle(
size: responsiveSize,
size: size,
imagePath: imagePath,
showOverlayText: isLast,
overlayText: moreText,
@@ -178,10 +174,10 @@ class _AvatarCircle extends StatelessWidget {
return Container(
width: size,
height: size,
padding: EdgeInsets.all(1.5.r),
padding: const EdgeInsets.all(1.5), // white ring thickness
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
color: Colors.white, // ring color
),
child: ClipOval(
child: Stack(
@@ -194,9 +190,9 @@ class _AvatarCircle extends StatelessWidget {
alignment: Alignment.center,
child: Text(
overlayText,
style: TextStyle(
style: const TextStyle(
color: Colors.white,
fontSize: 11.sp,
fontSize: 11,
fontWeight: FontWeight.w700,
),
),
@@ -207,3 +203,4 @@ class _AvatarCircle extends StatelessWidget {
);
}
}

View File

@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../common_packages/common_app_texts.dart';
import '../../core/route_constants.dart';
class ChooseYourPassSection extends StatefulWidget {
@@ -21,7 +20,7 @@ class _ChooseYourPassSectionState extends State<ChooseYourPassSection> {
final List<Map<String, dynamic>> passes = [
{
"title": "Chicago-\n${CommonAppText.selectiveCard} CARD",
"title": "Chicago-\nFLEXI CARD",
"price": "\$50",
"color": const Color(0xffF95FAF),
"bgColor": const Color(0xFFFDE7F1),

View File

@@ -19,158 +19,156 @@ class IntroScreensView extends StatelessWidget {
return BlocProvider(
create: (_) => IntroScreensCubit(),
child: Scaffold(
body: SafeArea(
child: BlocBuilder<IntroScreensCubit, IntroScreensState>(
builder: (context, state) {
return Stack(
children: [
// Background PageView
PageView.builder(
controller: _pageController,
itemCount: pages.length,
onPageChanged: (index) =>
context.read<IntroScreensCubit>().updatePage(index),
itemBuilder: (context, index) {
final page = pages[index];
return Stack(
fit: StackFit.expand,
children: [
Image.asset(page.image, fit: BoxFit.cover),
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFF00000000), Color(0xFF000000)],
),
body: BlocBuilder<IntroScreensCubit, IntroScreensState>(
builder: (context, state) {
return Stack(
children: [
// Background PageView
PageView.builder(
controller: _pageController,
itemCount: pages.length,
onPageChanged: (index) =>
context.read<IntroScreensCubit>().updatePage(index),
itemBuilder: (context, index) {
final page = pages[index];
return Stack(
fit: StackFit.expand,
children: [
Image.asset(page.image, fit: BoxFit.cover),
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [Color(0xFF00000000), Color(0xFF000000)],
),
),
],
);
},
),
// Skip Button (Only first 2 pages)
if (state.currentPage < pages.length - 1)
Positioned(
top: 50,
right: 20,
child: GestureDetector(
onTap: (){
Navigator.pushReplacementNamed(context,RouteConstants.home);
},
child: Container(
height: 48.h,
width: 92.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16.r),
border: Border.all(color: Colors.white),
),
child: Center(
child: Text(
'Skip',
style: TextStyle(
color: Colors.white,
fontSize: 16.sp,
fontWeight: FontWeight.w500,
),
),
],
);
},
),
// Skip Button (Only first 2 pages)
if (state.currentPage < pages.length - 1)
Positioned(
top: 50,
right: 20,
child: GestureDetector(
onTap: (){
Navigator.pushReplacementNamed(context,RouteConstants.home);
},
child: Container(
height: 48.h,
width: 92.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16.r),
border: Border.all(color: Colors.white),
),
child: Center(
child: Text(
'Skip',
style: TextStyle(
color: Colors.white,
fontSize: 16.sp,
fontWeight: FontWeight.w500,
),
),
),
),
),
// Bottom Content
Align(
alignment: Alignment.bottomCenter,
child: GlassMorphismContainer(
blurIntensity: 0.5,
padding: EdgeInsets.symmetric(
horizontal: 24.w,
vertical: 17.h,
),
margin: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
pages[state.currentPage].title,
),
// Bottom Content
Align(
alignment: Alignment.bottomCenter,
child: GlassMorphismContainer(
blurIntensity: 0.5,
padding: EdgeInsets.symmetric(
horizontal: 24.w,
vertical: 17.h,
),
margin: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
pages[state.currentPage].title,
style: TextStyle(
color: Colors.white,
fontSize: 24.sp,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
),
SizedBox(height: 8.h),
Text(
pages[state.currentPage].description,
style: TextStyle(
color: Colors.white,
fontSize: 14.sp,
),
textAlign: TextAlign.center,
),
SizedBox(height: 24.h),
// Dots Indicator
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
pages.length,
(index) => Container(
margin: const EdgeInsets.symmetric(
horizontal: 4.0,
),
width: 36.w,
height: 12.h,
decoration: BoxDecoration(
color: state.currentPage == index
? Color(0xFFF95F62)
: Color(0xFFF95F62).withOpacity(0.42),
borderRadius: BorderRadius.circular(12),
),
),
),
),
SizedBox(height: 24.h),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFFF95F62),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.r),
),
minimumSize: const Size(double.infinity, 52),
),
onPressed: () {
if (state.currentPage == pages.length - 1) {
Navigator.pushReplacementNamed(context, '/home');
} else {
_pageController.nextPage(
duration: const Duration(milliseconds: 400),
curve: Curves.easeInOut,
);
}
},
child: Text(
state.currentPage == pages.length - 1
? "Let's Get Started"
: 'Continue',
style: TextStyle(
color: Colors.white,
fontSize: 24.sp,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
),
SizedBox(height: 8.h),
Text(
pages[state.currentPage].description,
style: TextStyle(
color: Colors.white,
fontSize: 14.sp,
),
textAlign: TextAlign.center,
),
SizedBox(height: 24.h),
// Dots Indicator
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
pages.length,
(index) => Container(
margin: const EdgeInsets.symmetric(
horizontal: 4.0,
),
width: 36.w,
height: 12.h,
decoration: BoxDecoration(
color: state.currentPage == index
? Color(0xFFF95F62)
: Color(0xFFF95F62).withOpacity(0.42),
borderRadius: BorderRadius.circular(12),
),
),
fontSize: 20.sp,
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 24.h),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFFF95F62),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.r),
),
minimumSize: const Size(double.infinity, 52),
),
onPressed: () {
if (state.currentPage == pages.length - 1) {
Navigator.pushReplacementNamed(context, '/home');
} else {
_pageController.nextPage(
duration: const Duration(milliseconds: 400),
curve: Curves.easeInOut,
);
}
},
child: Text(
state.currentPage == pages.length - 1
? "Let's Get Started"
: 'Continue',
style: TextStyle(
color: Colors.white,
fontSize: 20.sp,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
],
),
),
],
);
},
),
),
],
);
},
),
),
);

View File

@@ -36,29 +36,28 @@ class _DietarySelectionViewState extends State<DietarySelectionView> {
Text(
"👋 Hello! We'd love to know more about you. Do you follow any dietary preferences?",
style: TextStyle(
color: const Color(0xFF101828),
fontSize: 20.sp,
color: Color(0xFF101828),
fontSize: 24.sp,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
),
SizedBox(height: 10.h),
SizedBox(height: 12.h),
CustomText(
text: "Select all that apply",
size: 12.sp,
size: 14.sp,
color: const Color(0xFF6A7282),
),
SizedBox(height: 32.h),
SizedBox(height: 38.h),
SizedBox(
height: 320.h,
height: 350.h,
child: BlocBuilder<AddItineraryDetailBloc, ItineraryDetailState>(
builder: (context, sate) {
return GridView.builder(
physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
physics: NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
mainAxisSpacing: 10.h,
crossAxisSpacing: 14.w,
mainAxisSpacing: 12,
crossAxisSpacing: 16,
crossAxisCount: 2,
childAspectRatio: 1.7,
),
@@ -73,15 +72,12 @@ class _DietarySelectionViewState extends State<DietarySelectionView> {
);
},
child: Container(
width: 150.w,
width: 168.w,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20.r),
borderRadius: BorderRadius.circular(24),
border: isSelected
? Border.all(
color: const Color(0xFFF95F62),
width: 1.5.w,
)
? Border.all(color: Color(0xFFF95F62))
: Border.all(color: Colors.transparent),
),
alignment: Alignment.center,
@@ -89,15 +85,11 @@ class _DietarySelectionViewState extends State<DietarySelectionView> {
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
item["icon"] ?? "",
width: 40.w,
height: 40.h,
),
SizedBox(height: 6.h),
Image.asset(item["icon"] ?? "", scale: 4),
SizedBox(height: 8),
CustomText(
text: item["name"] ?? "",
size: 14.sp,
size: 16.sp,
weight: FontWeight.w500,
color: const Color(0xFF364153),
),
@@ -111,7 +103,7 @@ class _DietarySelectionViewState extends State<DietarySelectionView> {
),
),
SizedBox(height: 36.h),
SizedBox(height: 41.h),
CustomFilledButton(
onTap: () {
context.read<ItineraryStepNavigationBloc>().add(

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

@@ -1,6 +0,0 @@
class ApiUrls {
static const baseUrl = "https://devapi.citycards.betadelivery.com";
static const cityList = "$baseUrl/mobile/city_list";
}

View File

@@ -1,192 +0,0 @@
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
class NetworkApiService {
static final NetworkApiService _instance = NetworkApiService._internal();
late Dio _dio;
factory NetworkApiService() {
return _instance;
}
NetworkApiService._internal() {
_dio = Dio(
BaseOptions(
connectTimeout: const Duration(seconds: 30),
receiveTimeout: const Duration(seconds: 30),
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
),
);
// Add interceptors
_dio.interceptors.add(
InterceptorsWrapper(
onRequest: (options, handler) {
// Add token if available
// String? token = "use token here from local shared preference";
// if (token != null) {
// options.headers['Authorization'] = 'Bearer $token';
// }
if (kDebugMode) {
print('REQUEST[${options.method}] => URL: ${options.uri}');
}
return handler.next(options);
},
onResponse: (response, handler) {
if (kDebugMode) {
print('RESPONSE[${response.statusCode}] => DATA: ${response.data}');
}
return handler.next(response);
},
onError: (error, handler) {
if (kDebugMode) {
print('ERROR[${error.response?.statusCode}] => MESSAGE: ${error.message}');
}
return handler.next(error);
},
),
);
// Add logging interceptor in debug mode
if (kDebugMode) {
_dio.interceptors.add(LogInterceptor(
request: true,
requestHeader: true,
requestBody: true,
responseHeader: false,
responseBody: true,
error: true,
));
}
}
// GET API Request
Future<Response> getApi({
required String url,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
}) async {
try {
final response = await _dio.get(
url,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
);
return response;
} on DioException catch (e) {
throw _handleError(e);
}
}
// POST API Request
Future<Response> postApi({
required String url,
dynamic data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
ProgressCallback? onSendProgress,
}) async {
try {
final response = await _dio.post(
url,
data: data,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
onSendProgress: onSendProgress,
);
return response;
} on DioException catch (e) {
throw _handleError(e);
}
}
// PUT API Request (Bonus)
Future<Response> putApi({
required String url,
dynamic data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
}) async {
try {
final response = await _dio.put(
url,
data: data,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
);
return response;
} on DioException catch (e) {
throw _handleError(e);
}
}
// DELETE API Request (Bonus)
Future<Response> deleteApi({
required String url,
dynamic data,
Map<String, dynamic>? queryParameters,
Options? options,
CancelToken? cancelToken,
}) async {
try {
final response = await _dio.delete(
url,
data: data,
queryParameters: queryParameters,
options: options,
cancelToken: cancelToken,
);
return response;
} on DioException catch (e) {
throw _handleError(e);
}
}
// Error Handler
String _handleError(DioException error) {
String errorDescription = "";
switch (error.type) {
case DioExceptionType.connectionTimeout:
errorDescription = "Connection timeout. Please try again.";
break;
case DioExceptionType.sendTimeout:
errorDescription = "Send timeout. Please try again.";
break;
case DioExceptionType.receiveTimeout:
errorDescription = "Receive timeout. Please try again.";
break;
case DioExceptionType.badCertificate:
errorDescription = "Bad certificate.";
break;
case DioExceptionType.badResponse:
errorDescription = error.response?.data['message'] ??
"Received invalid status code: ${error.response?.statusCode}";
break;
case DioExceptionType.cancel:
errorDescription = "Request was cancelled.";
break;
case DioExceptionType.connectionError:
errorDescription = "No internet connection.";
break;
case DioExceptionType.unknown:
errorDescription = "Something went wrong. Please try again.";
break;
}
return errorDescription;
}
// Update headers (e.g., add token)
void updateHeaders(Map<String, dynamic> headers) {
_dio.options.headers.addAll(headers);
}
}

View File

@@ -1,9 +1,10 @@
import 'package:citycards_customer/attraction_details/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/custom_bullet_points.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:share_plus/share_plus.dart';
class OfferPassDetailView extends StatelessWidget {
const OfferPassDetailView({super.key});
@@ -91,12 +92,16 @@ class OfferPassDetailView extends StatelessWidget {
right: 17.w,
child: GestureDetector(
onTap: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) => const ShareBottomSheet(),
Share.share(
'Check out my City Card app!',
subject: 'City Card App',
);
// showModalBottomSheet(
// context: context,
// isScrollControlled: true,
// backgroundColor: Colors.transparent,
// builder: (context) => const ShareBottomSheet(),
// );
},
child: Container(
height: 36.h,

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

@@ -7,8 +7,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../common_packages/common_app_texts.dart';
class SearchOffersWithListing extends StatelessWidget {
SearchOffersWithListing({super.key});
@@ -34,7 +32,7 @@ class SearchOffersWithListing extends StatelessWidget {
},
child: Icon(Icons.arrow_back)),
SizedBox(width: 8.w),
CustomText(text: "Offers with ${CommonAppText.selectiveCard} Card", size: 12.sp),
CustomText(text: "Offers with Flexi Card", size: 12.sp),
],
),
@@ -144,8 +142,6 @@ class SearchOffersWithListing extends StatelessWidget {
text: offer["description"] ?? "",
color: Colors.black.withOpacity(.6),
size: 12.sp,
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
],
),

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

View File

@@ -246,4 +246,4 @@
// ),
// );
// }
// }
// }

View File

@@ -129,30 +129,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.7.11"
dio:
dependency: "direct main"
description:
name: dio
sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
url: "https://pub.dev"
source: hosted
version: "5.9.0"
dio_web_adapter:
dependency: transitive
description:
name: dio_web_adapter
sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
dylib:
dependency: transitive
description:
name: dylib
sha256: bf609b3eb6492a3309b3d1dbe8f83a4031de5535dd7686be33487051cc760bb0
url: "https://pub.dev"
source: hosted
version: "0.3.3"
equatable:
dependency: "direct main"
description:
@@ -230,14 +206,6 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_angle:
dependency: transitive
description:
name: flutter_angle
sha256: "344a6b5ba7fa4893799ba8a98ed1b164e0b92ce77cf98b6c0f276efe1032249e"
url: "https://pub.dev"
source: hosted
version: "0.3.8"
flutter_bloc:
dependency: "direct main"
description:
@@ -589,14 +557,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.1.1"
logger:
dependency: transitive
description:
name: logger
sha256: a7967e31b703831a893bbc3c3dd11db08126fe5f369b5c648a36f821979f5be3
url: "https://pub.dev"
source: hosted
version: "2.6.2"
lottie:
dependency: "direct main"
description:
@@ -625,10 +585,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
url: "https://pub.dev"
source: hosted
version: "1.17.0"
version: "1.16.0"
mime:
dependency: transitive
description:
@@ -645,14 +605,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.0"
opentype_dart:
dependency: transitive
description:
name: opentype_dart
sha256: "4bd96aeed494289a87e92bde20afe60f59648dcef253c0a7159b65ffa23899dc"
url: "https://pub.dev"
source: hosted
version: "0.0.1"
package_info_plus:
dependency: transitive
description:
@@ -773,6 +725,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.0"
share_plus:
dependency: "direct main"
description:
name: share_plus
sha256: "14c8860d4de93d3a7e53af51bff479598c4e999605290756bbbe45cf65b37840"
url: "https://pub.dev"
source: hosted
version: "12.0.1"
share_plus_platform_interface:
dependency: transitive
description:
name: share_plus_platform_interface
sha256: "88023e53a13429bd65d8e85e11a9b484f49d4c190abbd96c7932b74d6927cc9a"
url: "https://pub.dev"
source: hosted
version: "6.1.0"
shared_preferences:
dependency: "direct main"
description:
@@ -926,106 +894,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
url: "https://pub.dev"
source: hosted
version: "0.7.7"
three_js:
dependency: "direct main"
description:
name: three_js
sha256: "76554d705ee0f7101d969773bc92fbd18d2e0f28ee87a9e41f7c0a86bd76f480"
url: "https://pub.dev"
source: hosted
version: "0.2.6"
three_js_advanced_loaders:
dependency: transitive
description:
name: three_js_advanced_loaders
sha256: "901ee356fba0ffe57547e448ee73aaa351b68a46608cf8f13c798b28980fbd38"
url: "https://pub.dev"
source: hosted
version: "0.2.4"
three_js_animations:
dependency: transitive
description:
name: three_js_animations
sha256: "7f7d708e96301e0fef1ab71ebaef280d5885c081f184e49c6956a22bd51ee444"
url: "https://pub.dev"
source: hosted
version: "0.2.1"
three_js_controls:
dependency: transitive
description:
name: three_js_controls
sha256: "4cc52d53e3bf1cca0d469c5f200b10c5c96954663d551bdb6b0b4278a0dca148"
url: "https://pub.dev"
source: hosted
version: "0.2.2"
three_js_core:
dependency: transitive
description:
name: three_js_core
sha256: "4aa78017f1562cd2c67b3146587eb8c6b75870ca030b0a5263d532d6660c31dd"
url: "https://pub.dev"
source: hosted
version: "0.2.6"
three_js_core_loaders:
dependency: transitive
description:
name: three_js_core_loaders
sha256: "85ca58d66b57b94acb5b4598e97ebd54a1679025f4eaf8cfa2dade4b561b2189"
url: "https://pub.dev"
source: hosted
version: "0.2.1"
three_js_curves:
dependency: transitive
description:
name: three_js_curves
sha256: "9f11f2fa869dc6b1aa01219bd0587b768cc0dcbf0bde5bbb576c5fabcbb3b3c7"
url: "https://pub.dev"
source: hosted
version: "0.2.1"
three_js_geometry:
dependency: transitive
description:
name: three_js_geometry
sha256: "11cbf2661b32fe90d7150c2dddd2846866de6500589e13eb9d682b017c19420a"
url: "https://pub.dev"
source: hosted
version: "0.2.1"
three_js_math:
dependency: transitive
description:
name: three_js_math
sha256: "8687016308b8d6d027a8fb3982eedb7514bbc4cf21145da0116dbd801254aa50"
url: "https://pub.dev"
source: hosted
version: "0.2.5"
three_js_sensors:
dependency: transitive
description:
name: three_js_sensors
sha256: "40874a52c6e617762d324e8149356ccacf90aa65a8904f6e3520e4aba1bff447"
url: "https://pub.dev"
source: hosted
version: "0.1.2"
three_js_simple_loaders:
dependency: transitive
description:
name: three_js_simple_loaders
sha256: "3728e84dd74cf68fbf7b6260d5eda47afbcddc48dd7b127a56fef85d92b87c78"
url: "https://pub.dev"
source: hosted
version: "0.2.2"
three_js_text:
dependency: transitive
description:
name: three_js_text
sha256: ba5f44e5da00b5f510a24308644da72c5d3b039542da3d1c13b08f7e28066d29
url: "https://pub.dev"
source: hosted
version: "0.2.0"
version: "0.7.6"
timezone:
dependency: transitive
description:
@@ -1050,6 +922,70 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.2.2"
url_launcher:
dependency: "direct main"
description:
name: url_launcher
sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8
url: "https://pub.dev"
source: hosted
version: "6.3.2"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: "5c8b6c2d89a78f5a1cca70a73d9d5f86c701b36b42f9c9dac7bad592113c28e9"
url: "https://pub.dev"
source: hosted
version: "6.3.24"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: "6b63f1441e4f653ae799166a72b50b1767321ecc263a57aadf825a7a2a5477d9"
url: "https://pub.dev"
source: hosted
version: "6.3.5"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
url: "https://pub.dev"
source: hosted
version: "3.2.1"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
sha256: "8262208506252a3ed4ff5c0dc1e973d2c0e0ef337d0a074d35634da5d44397c9"
url: "https://pub.dev"
source: hosted
version: "3.2.4"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77"
url: "https://pub.dev"
source: hosted
version: "3.1.4"
uuid:
dependency: transitive
description:
@@ -1070,10 +1006,10 @@ packages:
dependency: "direct main"
description:
name: video_player
sha256: "096bc28ce10d131be80dfb00c223024eb0fba301315a406728ab43dd99c45bdf"
sha256: "0d55b1f1a31e5ad4c4967bfaa8ade0240b07d20ee4af1dfef5f531056512961a"
url: "https://pub.dev"
source: hosted
version: "2.10.1"
version: "2.10.0"
video_player_android:
dependency: transitive
description:
@@ -1086,10 +1022,10 @@ packages:
dependency: transitive
description:
name: video_player_avfoundation
sha256: "03fc6d07dba2499588d30887329b399c1fe2d68ce4b7fcff0db79f44a2603f69"
sha256: "19ed1162a7a5520e7d7791e0b7b73ba03161b6a69428b82e4689e435b325432d"
url: "https://pub.dev"
source: hosted
version: "2.8.6"
version: "2.8.5"
video_player_platform_interface:
dependency: transitive
description:

View File

@@ -33,7 +33,6 @@ dependencies:
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
three_js: ^0.2.6
google_fonts: ^6.3.2
flutter_bloc: ^9.1.1
cupertino_icons: ^1.0.8
@@ -49,10 +48,11 @@ dependencies:
shared_preferences: ^2.5.3
flutter_launcher_icons: ^0.14.4
flutter_glass_morphism: ^1.0.2
video_player: ^2.10.0
lottie: ^3.3.2
flutter_native_splash: ^2.4.7
video_player: ^2.10.1
dio: ^5.9.0
url_launcher: ^6.3.2
share_plus: ^12.0.1
dev_dependencies:
flutter_test: