8 Commits

Author SHA1 Message Date
Vinayakkadge04
5c11344c17 Used Screen Util in ticket card view 2025-10-29 17:56:38 +05:30
9dd76e1dac Merge remote-tracking branch 'origin/vinayak' into dinesh
# Conflicts:
#	lib/main.dart
2025-10-29 17:12:05 +05:30
ede130224e working on make booking page 2025-10-29 17:09:04 +05:30
abca972ba5 Merge remote-tracking branch 'origin/vinayak' into dinesh
# Conflicts:
#	lib/main.dart
2025-10-29 16:13:43 +05:30
92ce97b553 working on my pass section 2025-10-29 16:13:10 +05:30
85c17595f2 changes in main 2025-10-28 15:54:38 +05:30
548c4e2638 Merge remote-tracking branch 'origin/vinayak' into dinesh 2025-10-28 15:53:48 +05:30
5c4ffb1686 Postcard changes done 2025-10-28 15:53:26 +05:30
69 changed files with 1881 additions and 377 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

View File

@@ -1,12 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

View File

@@ -1,12 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
</layer-list>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#F95F62</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -5,6 +5,10 @@
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#F95F62</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -5,6 +5,10 @@
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
assets/images/qr_image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"filename" : "background.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

View File

@@ -1,23 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@@ -16,13 +16,19 @@
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" image="LaunchBackground" translatesAutoresizingMaskIntoConstraints="NO" id="tWc-Dq-wcI"/>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4"></imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="3T2-ad-Qdv"/>
<constraint firstItem="tWc-Dq-wcI" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="RPx-PI-7Xg"/>
<constraint firstItem="tWc-Dq-wcI" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="SdS-ul-q2q"/>
<constraint firstAttribute="trailing" secondItem="tWc-Dq-wcI" secondAttribute="trailing" id="Swv-Gf-Rwn"/>
<constraint firstAttribute="trailing" secondItem="YRO-k0-Ey4" secondAttribute="trailing" id="TQA-XW-tRk"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="duK-uY-Gun"/>
<constraint firstItem="tWc-Dq-wcI" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="kV7-tw-vXt"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="xPn-NY-SIU"/>
</constraints>
</view>
</viewController>
@@ -32,6 +38,7 @@
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
<image name="LaunchImage" width="399" height="138"/>
<image name="LaunchBackground" width="1" height="1"/>
</resources>
</document>

View File

@@ -45,5 +45,7 @@
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIStatusBarHidden</key>
<false/>
</dict>
</plist>

View File

@@ -4,6 +4,8 @@ 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});
@@ -258,7 +260,11 @@ class AttractionDetailsView extends StatelessWidget {
),
SizedBox(height: 16.h),
Container(
InkWell(
onTap: (){
Navigator.of(context).pushNamed(RouteConstants.makeBooking);
},
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 24.w,
vertical: 18.h,
@@ -298,6 +304,7 @@ class AttractionDetailsView extends StatelessWidget {
],
),
),
),
SizedBox(height: 30.h),

View File

@@ -14,6 +14,11 @@ class AttractionsBloc extends Bloc<AttractionsEvent, AttractionsState> {
emit(AttractionsLoaded(attractions));
});
on<LoadMyPassAttraction>((event, emit) {
final attractions = repository.fetchMyPassAttraction();
emit(AttractionsLoaded(attractions));
});
on<SearchAttractions>((event, emit) {
if (state is AttractionsLoaded) {
final currentState = state as AttractionsLoaded;

View File

@@ -4,6 +4,8 @@ abstract class AttractionsEvent {}
class LoadAttractions extends AttractionsEvent {}
class LoadMyPassAttraction extends AttractionsEvent {}
class SearchAttractions extends AttractionsEvent {
final String query;
SearchAttractions(this.query);

View File

@@ -4,6 +4,8 @@ class Attraction {
final String price;
final String image;
final List<String> tags;
final bool isBookingRequired;
final String description;
Attraction({
required this.title,
@@ -11,5 +13,7 @@ class Attraction {
required this.price,
required this.image,
required this.tags,
required this.isBookingRequired,
required this.description
});
}

View File

@@ -9,6 +9,9 @@ class AttractionsRepository {
price: "\$25",
image: "assets/dummy/dummy_1.jpg",
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... ",
),
Attraction(
title: "Siem Reap",
@@ -16,6 +19,9 @@ class AttractionsRepository {
price: "\$25",
image: "assets/dummy/dummy_2.jpg",
tags: ["Unlimited 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... ",
),
Attraction(
title: "Dart Palace",
@@ -23,6 +29,9 @@ class AttractionsRepository {
price: "\$25",
image: "assets/dummy/dummy_3.jpg",
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... ",
),
Attraction(
title: "Koh Rong Samloem",
@@ -30,6 +39,9 @@ class AttractionsRepository {
price: "\$25",
image: "assets/dummy/dummy_4.jpg",
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... ",
),
Attraction(
title: "Dart Palace",
@@ -37,6 +49,64 @@ class AttractionsRepository {
price: "\$25",
image: "assets/dummy/dummy_5.jpg",
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... ",
),
];
}
List<Attraction> fetchMyPassAttraction() {
return [
Attraction(
title: "Koh Rong Samloem",
location: "Krong Siem Reap",
price: "\$25",
image: "assets/dummy/dummy_1.jpg",
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... ",
),
Attraction(
title: "Siem Reap",
location: "Krong Siem Reap",
price: "\$25",
image: "assets/dummy/dummy_2.jpg",
tags: ["Unlimited 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... ",
),
Attraction(
title: "Dart Palace",
location: "Krong Siem Reap",
price: "\$25",
image: "assets/dummy/dummy_3.jpg",
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... ",
),
Attraction(
title: "Koh Rong Samloem",
location: "Krong Siem Reap",
price: "\$25",
image: "assets/dummy/dummy_4.jpg",
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... ",
),
Attraction(
title: "Dart Palace",
location: "Krong Siem Reap",
price: "\$25",
image: "assets/dummy/dummy_5.jpg",
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,4 +1,5 @@
import 'package:citycards_customer/common_packages/app_bar.dart';
import 'package:citycards_customer/common_packages/back_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
@@ -9,12 +10,24 @@ import '../widget/attraction_card.dart';
import '../widget/filter_chip.dart';
class AttractionsPage extends StatelessWidget {
const AttractionsPage({super.key});
final String source;
const AttractionsPage({super.key, required this.source});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => AttractionsBloc(AttractionsRepository())..add(LoadAttractions()),
create: (_) {
final bloc = AttractionsBloc(AttractionsRepository());
// 🔥 Trigger event based on source
if (source == "home") {
bloc.add(LoadAttractions());
} else if (source == "qrPass") {
bloc.add(LoadMyPassAttraction());
}
return bloc;
},
child: BlocBuilder<AttractionsBloc, AttractionsState>(
builder: (context, state) {
final bloc = context.read<AttractionsBloc>();
@@ -29,26 +42,7 @@ class AttractionsPage extends StatelessWidget {
children: [
// App bar
CommonAppBar(isWhiteLogo: false, isProfilePage: false),
SizedBox(height: 22.h),
// Back row
Row(
children: [
GestureDetector(
onTap: () => Navigator.pop(context),
child: Icon(Icons.arrow_back, size: 24.sp),
),
SizedBox(width: 8.w),
Text(
"Your Attraction",
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.w400,
color: Colors.black87,
),
),
],
),
backWidget(context, "Your Attraction", Colors.black),
const SizedBox(height: 20),
// 🔍 Search field

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../core/route_constants.dart';
import '../models/attraction_model.dart';
class AttractionCard extends StatelessWidget {
@@ -8,7 +9,11 @@ class AttractionCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
return InkWell(
onTap: (){
Navigator.of(context).pushNamed(RouteConstants.attractionDetails);
},
child: Container(
margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
@@ -33,13 +38,19 @@ class AttractionCard extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(attraction.title,
style: TextStyle(
fontSize: 16, fontWeight: FontWeight.w500)),
Text(
attraction.title,
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
),
const SizedBox(height: 6),
Text(attraction.location,
Text(
attraction.location,
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Color(0xff464646))),
fontSize: 12,
fontWeight: FontWeight.w400,
color: Color(0xff464646),
),
),
const SizedBox(height: 6),
Text.rich(
TextSpan(
@@ -54,22 +65,32 @@ class AttractionCard extends StatelessWidget {
),
const TextSpan(
text: "/person",
style:
TextStyle(fontSize: 10, color: Colors.black, fontWeight: FontWeight.w400,),
style: TextStyle(
fontSize: 10,
color: Colors.black,
fontWeight: FontWeight.w400,
),
),
],
),
),
const SizedBox(height: 6),
Wrap(
attraction.isBookingRequired == false
? Wrap(
spacing: 6,
children: attraction.tags
.map((tag) => Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
.map(
(tag) => Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 4,
),
decoration: BoxDecoration(
color: tag == "Flexi Card"
? const Color(0xffF95FAF).withOpacity(0.1)
: const Color(0xffF95F62).withOpacity(0.1),
: const Color(
0xffF95F62,
).withOpacity(0.1),
border: Border.all(
color: tag == "Flexi Card"
? const Color(0xffF95FAF)
@@ -85,15 +106,37 @@ class AttractionCard extends StatelessWidget {
fontWeight: FontWeight.w400,
),
),
))
),
)
.toList(),
)
,
: Container(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 4,
),
decoration: BoxDecoration(
color: Color(0xffC1D2F8),
border: Border.all(
color: Color(0xff2563EB),
),
borderRadius: BorderRadius.circular(20),
),
child: Text(
"Booking Required",
style: GoogleFonts.poppins(
fontSize: 11,
color: Color(0xff1A1A1A),
fontWeight: FontWeight.w400,
),
),
),
],
),
),
],
),
),
);
}
}

View File

@@ -14,26 +14,23 @@ class TicketCard extends StatelessWidget {
clipper: TicketClipper(),
child: Container(
width: 270.w,
height: 410.h,
height: 400.h,
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.r),
),
child: Column(
children: [
// Image Section
ClipRRect(
borderRadius: BorderRadius.circular(12.r),
child: Image.asset(
'assets/images/card_banner.png',
width: double.infinity,
width: 237.w,
height: 198.h,
fit: BoxFit.cover,
),
),
SizedBox(height: 24.h),
// Dashed divider
SizedBox(height: 20.h),
SizedBox(
width: 200.w,
child: DashedDivider(
@@ -41,7 +38,7 @@ class TicketCard extends StatelessWidget {
thickness: 2.h,
),
),
SizedBox(height: 6.h),
Text(
"Melbourne",
style: TextStyle(
@@ -49,7 +46,7 @@ class TicketCard extends StatelessWidget {
fontWeight: FontWeight.w500,
),
),
SizedBox(height: 12.h),
SizedBox(height: 6.h),
_infoRow("Postcards :", "5"),
_infoRow("Date :", "22/04/2025"),
_infoRow("Time :", "12:00PM - 2:00PM"),
@@ -84,36 +81,39 @@ class TicketCard extends StatelessWidget {
class TicketPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
const notchRadius = 23.0;
const dividerY = 222.0;
final notchRadius = 23.r;
final dividerY = 240.h;
final ticketPath = Path()
..moveTo(12, 0)
..lineTo(size.width - 12, 0)
..arcToPoint(Offset(size.width, 12), radius: const Radius.circular(12))
..moveTo(12.w, 0)
..lineTo(size.width - 12.w, 0)
..arcToPoint(Offset(size.width, 12.h), radius: Radius.circular(12.r))
..lineTo(size.width, dividerY - notchRadius)
..arcToPoint(
Offset(size.width, dividerY + notchRadius),
radius: const Radius.circular(notchRadius),
radius: Radius.circular(notchRadius),
clockwise: false,
)
..lineTo(size.width, size.height - 12)
..arcToPoint(Offset(size.width - 12, size.height),
radius: const Radius.circular(12))
..lineTo(12, size.height)
..arcToPoint(Offset(0, size.height - 12),
radius: const Radius.circular(12))
..lineTo(size.width, size.height - 12.h)
..arcToPoint(
Offset(size.width - 12.w, size.height),
radius: Radius.circular(12.r),
)
..lineTo(12.w, size.height)
..arcToPoint(
Offset(0, size.height - 12.h),
radius: Radius.circular(12.r),
)
..lineTo(0, dividerY + notchRadius)
..arcToPoint(
Offset(0, dividerY - notchRadius),
radius: const Radius.circular(notchRadius),
radius: Radius.circular(notchRadius),
clockwise: false,
)
..lineTo(0, 12)
..arcToPoint(Offset(12, 0), radius: const Radius.circular(12))
..lineTo(0, 12.h)
..arcToPoint(Offset(12.w, 0), radius: Radius.circular(12.r))
..close();
// 🌑 Draw even soft black shadow around all sides
final shadowPaint = Paint()
..color = Colors.black.withOpacity(0.3)
..maskFilter = const MaskFilter.blur(BlurStyle.outer, 8);
@@ -134,33 +134,37 @@ class TicketPainter extends CustomPainter {
class TicketClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
const notchRadius = 23.0;
const dividerY = 222.0;
final notchRadius = 23.r;
final dividerY = 240.h;
final path = Path()
..moveTo(12, 0)
..lineTo(size.width - 12, 0)
..arcToPoint(Offset(size.width, 12), radius: const Radius.circular(12))
..moveTo(12.w, 0)
..lineTo(size.width - 12.w, 0)
..arcToPoint(Offset(size.width, 12.h), radius: Radius.circular(12.r))
..lineTo(size.width, dividerY - notchRadius)
..arcToPoint(
Offset(size.width, dividerY + notchRadius),
radius: const Radius.circular(notchRadius),
radius: Radius.circular(notchRadius),
clockwise: false,
)
..lineTo(size.width, size.height - 12)
..arcToPoint(Offset(size.width - 12, size.height),
radius: const Radius.circular(12))
..lineTo(12, size.height)
..arcToPoint(Offset(0, size.height - 12),
radius: const Radius.circular(12))
..lineTo(size.width, size.height - 12.h)
..arcToPoint(
Offset(size.width - 12.w, size.height),
radius: Radius.circular(12.r),
)
..lineTo(12.w, size.height)
..arcToPoint(
Offset(0, size.height - 12.h),
radius: Radius.circular(12.r),
)
..lineTo(0, dividerY + notchRadius)
..arcToPoint(
Offset(0, dividerY - notchRadius),
radius: const Radius.circular(notchRadius),
radius: Radius.circular(notchRadius),
clockwise: false,
)
..lineTo(0, 12)
..arcToPoint(Offset(12, 0), radius: const Radius.circular(12))
..lineTo(0, 12.h)
..arcToPoint(Offset(12.w, 0), radius: Radius.circular(12.r))
..close();
return path;

View File

@@ -44,7 +44,8 @@ class AppRouter {
},
);
case RouteConstants.attractionsPage:
return MaterialPageRoute(builder: (_) => const AttractionsPage());
final args = settings.arguments as String;
return MaterialPageRoute(builder: (_) => AttractionsPage(source: args));
case RouteConstants.profile:
return MaterialPageRoute(
builder: (_) {

View File

@@ -1,11 +1,17 @@
import 'package:citycards_customer/core/route_constants.dart';
import 'package:citycards_customer/my_pass/blocs/my_pass_bloc.dart';
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 '../attractions/views/attractions_page_view.dart';
import '../my_pass/views/booking_page_view.dart';
import '../my_pass/views/qr_pass_page_view.dart';
import '../postcard/blocs/postcard_creation_bloc.dart';
import '../postcard/views/postcard_creation_page_view.dart';
import '../search_offers/bloc/search_offers_listing_bloc.dart';
import '../search_offers/view/search_offers_with_listing.dart';
Widget buildOffstageNavigator(
int index,
@@ -24,8 +30,29 @@ Widget buildOffstageNavigator(
// 🔹 Attractions Page
case RouteConstants.attractionsPage:
final args = settings.arguments as String;
return MaterialPageRoute(
builder: (_) => const AttractionsPage(),
builder: (_) => AttractionsPage(source: args,),
);
case RouteConstants.attractionDetails:
return MaterialPageRoute(builder: (_) {
return AttractionDetailsView();
});
case RouteConstants.makeBooking:
return MaterialPageRoute(builder: (_) {
return MakeBookingView(title: 'asffdsf', description: 'afdsfadsfasdfads',);
});
case RouteConstants.searchOffer:
return MaterialPageRoute(
builder: (_) {
return BlocProvider(
create: (_) => OffersBloc(),
child: SearchOffersWithListing(),
);
},
);
// 🔹 Upload Photo Page (start of postcard creation flow)
@@ -49,6 +76,18 @@ Widget buildOffstageNavigator(
},
);
case RouteConstants.qrPage:
return MaterialPageRoute(
builder: (context) {
final previousBloc = BlocProvider.of<MyPassBloc>(context);
return BlocProvider.value(
value: previousBloc,
child: const QrPassView(),
);
},
);
default:
return MaterialPageRoute(
builder: (_) => const Scaffold(

View File

@@ -46,4 +46,7 @@ class RouteConstants {
static const String cartPage = '/cartPage';
static const String yourItinerary = '/yourItinerary';
static const String qrPage = '/qrPage';
static const String makeBooking = '/makeBooking';
}

View File

@@ -1,4 +1,3 @@
import 'package:citycards_customer/home/widgets/search_city_bottomsheet.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

View File

@@ -5,6 +5,7 @@ import '../../common_bloc/bottom_navigation_bloc.dart';
import '../../common_packages/custom_bottom_navbar.dart';
import '../../core/inside_bottom_navigator.dart';
import '../../itinerary_creation/views/itinerary_creation_start_view.dart';
import '../../my_pass/views/my_pass_page_view.dart';
import '../../postcard/views/postcard_initial_page_view.dart';
import 'first_time_user_home_page.dart';
@@ -36,6 +37,7 @@ class _HomePageState extends State<HomePage> {
children: [
buildOffstageNavigator(0, currentIndex, const FirstTimeUserHomePage(), _navigatorKeys[0]),
buildOffstageNavigator(1, currentIndex, const ItineraryCreationStartPage(), _navigatorKeys[1]),
buildOffstageNavigator(2, currentIndex, const MyPassesView(), _navigatorKeys[2]),
buildOffstageNavigator(3, currentIndex, const PostcardPage(), _navigatorKeys[3]),
],
),

View File

@@ -127,7 +127,7 @@ class _RegisteredUserHomePageState extends State<RegisteredUserHomePage> {
),
InkWell(
onTap: (){
Navigator.of(context).pushNamed(RouteConstants.attractionsPage);
Navigator.of(context).pushNamed(RouteConstants.attractionsPage, arguments: "home");
},
child: Text("View all",
style: TextStyle(

View File

@@ -1,12 +1,15 @@
import 'package:citycards_customer/cart/blocs/postcard_bloc.dart';
import 'package:flutter/material.dart';
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 'core/app_router.dart';
import 'core/route_constants.dart';
import 'my_pass/blocs/my_pass_bloc.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarColor: Colors.white,
@@ -14,6 +17,7 @@ void main() {
statusBarBrightness: Brightness.light,
),
);
runApp(MyApp());
}
@@ -27,9 +31,14 @@ class MyApp extends StatelessWidget {
return ScreenUtilInit(
designSize: const Size(390, 844),
builder: (context, child) {
return MaterialApp(
return MultiBlocProvider(
providers: [
BlocProvider<MyPassBloc>(
create: (_) => MyPassBloc()..add(LoadMyPasses()),
),
],
child: MaterialApp(
onGenerateRoute: _appRouter.onGenerateRoute,
initialRoute: RouteConstants.home,
debugShowCheckedModeBanner: false,
title: 'City Cards',
theme: ThemeData(
@@ -37,6 +46,7 @@ class MyApp extends StatelessWidget {
Theme.of(context).textTheme,
),
),
),
);
},
);

View File

@@ -0,0 +1,35 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'make_booking_events.dart';
import 'make_booking_state.dart';
class MakeBookingBloc extends Bloc<MakeBookingEvent, MakeBookingState> {
MakeBookingBloc() : super(const MakeBookingState(loading: true)) {
on<LoadAvailableDates>(_onLoadAvailableDates);
on<SelectDate>(_onSelectDate);
}
void _onLoadAvailableDates(
LoadAvailableDates event, Emitter<MakeBookingState> emit) async {
emit(state.copyWith(loading: true));
// Simulate API load delay
await Future.delayed(const Duration(milliseconds: 500));
// Dummy available dates
final now = DateTime.now();
final available = [
now.add(const Duration(days: 2)),
now.add(const Duration(days: 5)),
now.add(const Duration(days: 7)),
now.add(const Duration(days: 10)),
now.add(const Duration(days: 11)),
now.add(const Duration(days: 13)),
];
emit(state.copyWith(availableDates: available, loading: false));
}
void _onSelectDate(SelectDate event, Emitter<MakeBookingState> emit) {
emit(state.copyWith(startDate: event.startDate, endDate: event.endDate));
}
}

View File

@@ -0,0 +1,18 @@
import 'package:equatable/equatable.dart';
abstract class MakeBookingEvent extends Equatable {
@override
List<Object?> get props => [];
}
class LoadAvailableDates extends MakeBookingEvent {}
class SelectDate extends MakeBookingEvent {
final DateTime startDate;
final DateTime endDate;
SelectDate(this.startDate, this.endDate);
@override
List<Object?> get props => [startDate, endDate];
}

View File

@@ -0,0 +1,32 @@
import 'package:equatable/equatable.dart';
class MakeBookingState extends Equatable {
final List<DateTime> availableDates;
final DateTime? startDate;
final DateTime? endDate;
final bool loading;
const MakeBookingState({
this.availableDates = const [],
this.startDate,
this.endDate,
this.loading = false,
});
MakeBookingState copyWith({
List<DateTime>? availableDates,
DateTime? startDate,
DateTime? endDate,
bool? loading,
}) {
return MakeBookingState(
availableDates: availableDates ?? this.availableDates,
startDate: startDate ?? this.startDate,
endDate: endDate ?? this.endDate,
loading: loading ?? this.loading,
);
}
@override
List<Object?> get props => [availableDates, startDate, endDate, loading];
}

View File

@@ -0,0 +1,55 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import '../models/my_pass_model.dart';
abstract class MyPassEvent {}
class LoadMyPasses extends MyPassEvent {}
abstract class MyPassState {}
class MyPassLoading extends MyPassState {}
class SelectPass extends MyPassEvent {
final MyPassModel selectedPass;
SelectPass(this.selectedPass);
}
class MyPassEmpty extends MyPassState {}
class MyPassLoaded extends MyPassState {
final List<MyPassModel> passes;
final MyPassModel? selectedPass;
MyPassLoaded(this.passes, this.selectedPass);
}
class MyPassBloc extends Bloc<MyPassEvent, MyPassState> {
MyPassBloc() : super(MyPassLoading()) {
on<LoadMyPasses>((event, emit) async {
await Future.delayed(const Duration(milliseconds: 500));
final List<MyPassModel> passes = [
MyPassModel(
imageUrl:
"assets/images/city_melbourne.png",
title: "Unlimited Card",
city: "Melbourne",
validity: "20/09/2025",
adults: 3,
kids: 3,
duration: "2 Days",
isActive: true,
),
];
// If no passes, show empty screen
if (passes.isEmpty) {
emit(MyPassEmpty());
} else {
emit(MyPassLoaded(passes, null));
}
});
on<SelectPass>((event, emit) {
if (state is MyPassLoaded) {
final current = state as MyPassLoaded;
emit(MyPassLoaded(current.passes, event.selectedPass));
}
});
}
}

View File

@@ -0,0 +1,21 @@
class MyPassModel {
final String imageUrl;
final String title;
final String city;
final String validity;
final int adults;
final int kids;
final String duration;
final bool isActive;
MyPassModel({
required this.imageUrl,
required this.title,
required this.city,
required this.validity,
required this.adults,
required this.kids,
required this.duration,
required this.isActive,
});
}

View File

@@ -0,0 +1,245 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:table_calendar/table_calendar.dart';
import '../blocs/make_booking_bloc.dart';
import '../blocs/make_booking_events.dart';
import '../blocs/make_booking_state.dart';
class MakeBookingView extends StatelessWidget {
final String title;
final String description;
const MakeBookingView({
super.key,
required this.title,
required this.description,
});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => MakeBookingBloc()..add(LoadAvailableDates()),
child: BlocBuilder<MakeBookingBloc, MakeBookingState>(
builder: (context, state) {
if (state.loading) {
return const Center(child: CircularProgressIndicator());
}
final bloc = context.read<MakeBookingBloc>();
final now = DateTime.now();
return SafeArea(
child: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 20.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 🔙 Back + Title
GestureDetector(
onTap: () => Navigator.pop(context),
child: Row(
children: [
const Icon(Icons.arrow_back, size: 20),
SizedBox(width: 6.w),
Text(
"Make Booking",
style: GoogleFonts.poppins(
fontSize: 14.sp,
fontWeight: FontWeight.w500,
color: Colors.black,
),
),
],
),
),
SizedBox(height: 20.h),
// 🏝 Attraction title
Text(
title,
style: GoogleFonts.poppins(
fontSize: 18.sp,
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
SizedBox(height: 4.h),
// Description
Text(
description,
style: GoogleFonts.poppins(
fontSize: 12.sp,
color: Colors.black54,
),
),
SizedBox(height: 24.h),
// 📅 Calendar
Container(
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20.r),
boxShadow: [
BoxShadow(
color: Colors.black12.withOpacity(0.05),
blurRadius: 6,
offset: const Offset(0, 3),
),
],
),
child: Column(
children: [
Text(
"When are you visiting?",
style: GoogleFonts.poppins(
fontSize: 14.sp,
fontWeight: FontWeight.w500,
),
),
SizedBox(height: 10.h),
TableCalendar(
focusedDay: now,
firstDay: now,
lastDay: now.add(const Duration(days: 365)),
calendarFormat: CalendarFormat.month,
availableCalendarFormats: const {
CalendarFormat.month: 'Month'
},
rangeStartDay: state.startDate,
rangeEndDay: state.endDate,
rangeSelectionMode: RangeSelectionMode.toggledOn,
onRangeSelected: (start, end, focusedDay) {
if (start != null && end != null) {
bloc.add(SelectDate(start, end));
}
},
headerStyle: HeaderStyle(
titleCentered: true,
formatButtonVisible: false,
titleTextStyle: GoogleFonts.poppins(
fontSize: 14.sp,
fontWeight: FontWeight.w600,
),
),
calendarStyle: CalendarStyle(
rangeHighlightColor:
const Color(0xffFF5A5F).withOpacity(0.2),
rangeStartDecoration: const BoxDecoration(
color: Color(0xffFF5A5F),
shape: BoxShape.circle,
),
rangeEndDecoration: const BoxDecoration(
color: Color(0xffFF5A5F),
shape: BoxShape.circle,
),
todayDecoration: const BoxDecoration(
color: Color(0xffFFEAEA),
shape: BoxShape.circle,
),
outsideDaysVisible: false,
),
// Custom day builder for unavailable days
calendarBuilders: CalendarBuilders(
defaultBuilder: (context, day, focusedDay) {
final isAvailable = state.availableDates
.any((d) => isSameDay(d, day));
if (!isAvailable) {
// ❌ Strike-through unavailable date
return Stack(
alignment: Alignment.center,
children: [
Text(
'${day.day}',
style: GoogleFonts.poppins(
fontSize: 12.sp,
color: Colors.grey.shade400,
),
),
Positioned(
top: 12.h,
child: Container(
width: 14.w,
height: 1.2.h,
color: Colors.grey.shade400,
),
),
],
);
}
// ✅ Normal available day
return Center(
child: Text(
'${day.day}',
style: GoogleFonts.poppins(
fontSize: 12.sp,
color: Colors.black87,
),
),
);
},
),
),
],
),
),
SizedBox(height: 40.h),
// ✅ Confirm Booking button
GestureDetector(
onTap: () {
if (state.startDate != null && state.endDate != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
"Booking confirmed from "
"${state.startDate!.toLocal().toString().split(' ')[0]} "
"to ${state.endDate!.toLocal().toString().split(' ')[0]}",
),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Please select a valid date range"),
),
);
}
},
child: Container(
width: double.infinity,
padding: EdgeInsets.symmetric(vertical: 14.h),
decoration: BoxDecoration(
color: const Color(0xffFF5A5F),
borderRadius: BorderRadius.circular(30.r),
),
child: Center(
child: Text(
"Confirm Booking",
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: 14.sp,
fontWeight: FontWeight.w600,
),
),
),
),
),
],
),
),
);
},
),
);
}
}

View File

@@ -0,0 +1,168 @@
import 'package:citycards_customer/common_packages/app_bar.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../core/route_constants.dart';
import '../blocs/my_pass_bloc.dart';
import '../widgets/pass_widget.dart';
class MyPassesView extends StatelessWidget {
const MyPassesView({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<MyPassBloc, MyPassState>(
builder: (context, state) {
if (state is MyPassLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is MyPassEmpty) {
return _noPassView(context);
} else if (state is MyPassLoaded) {
return _passListView(state.passes);
}
return const SizedBox.shrink();
},
);
}
Widget _noPassView(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 30.h),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
'assets/images/no_pass.png', // your woman sitting image
height: 180.h,
),
SizedBox(height: 20.h),
Text(
"You Dont have a Pass Yet! 😕",
style: GoogleFonts.poppins(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: Colors.black,
),
textAlign: TextAlign.center,
),
SizedBox(height: 8.h),
Text(
"Get a pass and get offers and discounts and\nmore on your trip to your favourite city",
style: GoogleFonts.poppins(fontSize: 12.sp, color: Colors.black54),
textAlign: TextAlign.center,
),
SizedBox(height: 24.h),
GestureDetector(
onTap: () {
// Navigate to Buy a Pass
Navigator.pushNamed(context, '/buyPass');
},
child: Container(
width: double.infinity,
padding: EdgeInsets.symmetric(vertical: 14.h),
decoration: BoxDecoration(
color: const Color(0xffFF5A5F),
borderRadius: BorderRadius.circular(30.r),
),
child: Center(
child: Text(
"Buy a Pass",
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: 14.sp,
fontWeight: FontWeight.w600,
),
),
),
),
),
],
),
);
}
Widget _passListView(List passes) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: SingleChildScrollView(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CommonAppBar(isWhiteLogo: false, isProfilePage: false),
SizedBox(height: 10.h),
Row(
children: [
Container(
width: 130.w,
height: 36.h,
padding: EdgeInsets.symmetric(horizontal: 12.w),
decoration: BoxDecoration(
color: const Color(0xffFEE7E7),
borderRadius: BorderRadius.circular(8.r),
border: Border.all(color: const Color(0xffFDCDCE)),
),
child: Row(
children: [
Text(
"Sort by Date",
style: GoogleFonts.poppins(fontSize: 12.sp),
),
const Spacer(),
const Icon(Icons.sort, size: 16),
],
),
),
SizedBox(width: 10.w),
Container(
height: 36.h,
width: 130.w,
padding: EdgeInsets.symmetric(horizontal: 12.w),
decoration: BoxDecoration(
color: const Color(0xffFEE7E7),
borderRadius: BorderRadius.circular(8.r),
border: Border.all(color: const Color(0xffFDCDCE)),
),
child: Row(
children: [
Text(
"All",
style: GoogleFonts.poppins(fontSize: 12.sp),
),
const Spacer(),
const Icon(Icons.keyboard_arrow_down_rounded, size: 18),
],
),
),
],
),
SizedBox(height: 20.h),
ListView.builder(
itemCount: passes.length,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
final pass = passes[index];
return Padding(
padding: EdgeInsets.only(bottom: 16.h),
child: InkWell(
onTap: (){
context.read<MyPassBloc>().add(SelectPass(pass));
Navigator.of(
context,
).pushNamed(RouteConstants.qrPage);
},
child: PassTicketCard(pass: pass),
),
);
},
),
],
),
),
),
);
}
}

View File

@@ -0,0 +1,144 @@
import 'package:citycards_customer/my_pass/blocs/my_pass_bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../common_packages/app_bar.dart';
import '../../common_packages/back_widget.dart';
import '../../core/route_constants.dart';
import '../widgets/action_button_widget.dart';
import '../widgets/qr_container_widget.dart';
class QrPassView extends StatelessWidget {
const QrPassView({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<MyPassBloc, MyPassState>(
builder: (context, state) {
if (state is MyPassLoaded) {
final pass = state.selectedPass!;
return SafeArea(
child: Scaffold(
backgroundColor: Colors.white,
body: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 20.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CommonAppBar(isWhiteLogo: false, isProfilePage: false),
SizedBox(height: 10.h),
backWidget(context, "Back", Colors.black),
SizedBox(height: 20.h),
SizedBox(height: 10.h),
Text(
"Scan this at the site of\nattraction",
textAlign: TextAlign.center,
style: GoogleFonts.poppins(
fontSize: 13.sp,
color: Colors.black87,
),
),
SizedBox(height: 20.h),
/// ♻️ Reusable QR Container Component
QrContainerWidget(
qrImagePath: "assets/images/qr_image.png",
cityCardTitle: "Melbourne CityCards",
qrCode: "IYFHHVN254ADSD",
cardType: pass.title,
),
SizedBox(height: 24.h),
/// 🎟 Card details section
Container(
padding: EdgeInsets.symmetric(
vertical: 10,
horizontal: 40,
),
decoration: BoxDecoration(
color: pass.title.toLowerCase() == "unlimited card"
? const Color(0xffF95F62).withOpacity(0.1)
: const Color(0xffF95FAF).withOpacity(0.1),
borderRadius: BorderRadius.circular(25.r),
border: Border.all(
color: pass.title.toLowerCase() == "unlimited card"
? const Color(0xffF95F62)
: const Color(0xffF95FAF),
),
),
child: Text(
pass.title,
style: GoogleFonts.poppins(
fontSize: 16.sp,
color: const Color(0xffFF5A5F),
fontWeight: FontWeight.w500,
),
),
),
SizedBox(height: 6.h),
Text(
"Adults-${pass.adults} • Kids-${pass.kids}${pass.duration}",
style: GoogleFonts.poppins(
fontSize: 12.sp,
color: Color(0xff212121),
fontWeight: FontWeight.w400,
),
),
SizedBox(height: 4.h),
Text(
"Valid Till: ${pass.validity}",
style: GoogleFonts.poppins(
fontSize: 12.sp,
color: Color(0xff212121),
fontWeight: FontWeight.w400,
),
),
SizedBox(height: 28.h),
Align(
alignment: Alignment.centerLeft,
child: Text(
"Learn about policies",
style: GoogleFonts.poppins(
color: Colors.black,
fontSize: 12.sp,
fontWeight: FontWeight.w500,
decoration: TextDecoration.underline,
),
),
),
SizedBox(height: 24.h),
/// 🔘 Buttons
Column(
children: [
actionButton(
label: "View All Attractions",
onPressed: () {
Navigator.of(context).pushNamed(RouteConstants.attractionsPage, arguments: "qrPass");
},
),
SizedBox(height: 12.h),
actionButton(
label: "View All Available Offers",
onPressed: () {
Navigator.of(context).pushNamed(RouteConstants.searchOffer);
},
),
],
),
],
),
),
),
);
} else {
return const Center(child: CircularProgressIndicator());
}
},
);
}
}

View File

@@ -0,0 +1,40 @@
import "package:flutter/material.dart";
import "package:flutter_screenutil/flutter_screenutil.dart";
import "package:google_fonts/google_fonts.dart";
Widget actionButton({
required String label,
required VoidCallback onPressed,
}) {
return GestureDetector(
onTap: onPressed,
child: Container(
width: double.infinity,
padding: EdgeInsets.symmetric(vertical: 14.h, horizontal: 14.w),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.r),
color: const Color(0xffFFF5F5),
border: Border.all(color: const Color(0xffF5C2C2)),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
label,
style: GoogleFonts.poppins(
color: Colors.black87,
fontWeight: FontWeight.w500,
fontSize: 13.sp,
),
),
SizedBox(width: 4.w),
const Icon(
Icons.arrow_forward_ios_rounded,
size: 14,
color: Colors.black54,
),
],
),
),
);
}

View File

@@ -0,0 +1,285 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:google_fonts/google_fonts.dart';
class PassTicketCard extends StatelessWidget {
final dynamic pass;
const PassTicketCard({super.key, required this.pass});
@override
Widget build(BuildContext context) {
// Dimensions tuned to your screenshot
final double cardWidth = MediaQuery.of(context).size.width - 32.w;
final double topSectionHeight = 105.h; // where dotted line sits
final double bottomSectionHeight = 50.h;
final double cardHeight = topSectionHeight + bottomSectionHeight;
return SizedBox(
width: cardWidth,
child: CustomPaint(
// paints white background, border, corner radius, side cuts, shadow, and divider dots
painter: _TicketBackgroundPainter(
cornerRadius: 16.r,
notchRadius: 9.r,
dividerY: topSectionHeight,
borderColor: Colors.white,
shadowColor: Colors.black.withOpacity(0.08),
),
child: ClipPath(
// actual clipping so child content never bleeds outside the shape
clipper: _TicketClipper(
cornerRadius: 16.r,
notchRadius: 9.r,
dividerY: topSectionHeight,
),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 12.h),
child: Column(
children: [
// ---------- TOP SECTION ----------
SizedBox(
height: topSectionHeight - 12.h, // keep space for the dots line
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// thumbnail
ClipRRect(
borderRadius: BorderRadius.circular(10.r),
child: Image.asset(
pass.imageUrl,
height: 80.h,
width: 80.w,
fit: BoxFit.cover,
),
),
SizedBox(width: 10.w),
// details
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
if (pass.isActive)
Container(
padding: EdgeInsets.symmetric(
horizontal: 8.w, vertical: 3.h),
decoration: BoxDecoration(
color: const Color(0xff439F6E),
borderRadius: BorderRadius.circular(30.r),
),
child: Text(
"Active",
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: 10.sp,
fontWeight: FontWeight.w400,
),
),
),
SizedBox(width: 8.w),
Text(
pass.duration, // "2 Days"
style: GoogleFonts.poppins(
color: Colors.black87,
fontSize: 12.sp,
),
),
],
),
SizedBox(height: 10.h),
Text(
pass.title,
style: GoogleFonts.poppins(
fontWeight: FontWeight.w600,
fontSize: 18.sp,
height: 1.1,
),
),
SizedBox(height: 4.h),
Text(
"Adults-${pass.adults} • Kids-${pass.kids}",
style: GoogleFonts.poppins(
color: Colors.black54,
fontSize: 11.sp,
),
),
],
),
),
// QR chip
CircleAvatar(
radius: 20.r,
backgroundColor: Color(0xffFEE7E7),
child: Image.asset(
"assets/images/qr_image.png",
scale: 6,
),
)
],
),
),
// space exactly where the dotted line is painted by the painter
SizedBox(height: 15.h),
// ---------- BOTTOM SECTION ----------
Padding(
padding: EdgeInsets.symmetric(horizontal: 4.w),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Valid Till: ${pass.validity}",
style: GoogleFonts.poppins(
fontSize: 11.sp,
color: Colors.black,
fontWeight: FontWeight.w400
),
),
Text(
pass.city, // "Melbourne"
style: GoogleFonts.poppins(
fontWeight: FontWeight.w500,
fontSize: 13.sp,
),
),
],
),
),
],
),
),
),
),
);
}
}
/// Clips the ticket with rounded corners and 2 side “cuts” centered at dividerY
class _TicketClipper extends CustomClipper<Path> {
final double cornerRadius;
final double notchRadius;
final double dividerY;
_TicketClipper({
required this.cornerRadius,
required this.notchRadius,
required this.dividerY,
});
@override
Path getClip(Size size) {
final rrectPath = Path()
..addRRect(RRect.fromRectAndRadius(
Rect.fromLTWH(0, 0, size.width, size.height),
Radius.circular(cornerRadius),
));
final cuts = Path()
..addOval(Rect.fromCircle(center: Offset(0, dividerY), radius: notchRadius))
..addOval(Rect.fromCircle(center: Offset(size.width, dividerY), radius: notchRadius));
// Rounded-rect MINUS the two circles
return Path.combine(PathOperation.difference, rrectPath, cuts);
}
@override
bool shouldReclip(covariant _TicketClipper old) =>
cornerRadius != old.cornerRadius ||
notchRadius != old.notchRadius ||
dividerY != old.dividerY;
}
/// Paints fill, border, shadow and the dotted perforation line
class _TicketBackgroundPainter extends CustomPainter {
final double cornerRadius;
final double notchRadius;
final double dividerY;
final Color borderColor;
final Color shadowColor;
_TicketBackgroundPainter({
required this.cornerRadius,
required this.notchRadius,
required this.dividerY,
required this.borderColor,
required this.shadowColor,
});
Path _ticketPath(Size size) {
final clipper = _TicketClipper(
cornerRadius: cornerRadius,
notchRadius: notchRadius,
dividerY: dividerY,
);
return clipper.getClip(size);
}
@override
void paint(Canvas canvas, Size size) {
final path = _ticketPath(size);
// Realistic layered shadow
canvas.save();
canvas.translate(0, 2); // tiny downward offset for depth
final shadowPaint = Paint()
..color = Colors.black.withOpacity(0.10)
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 6);
canvas.drawPath(path, shadowPaint);
canvas.restore();
// Subtle ambient shadow (light spread around)
final ambientShadowPaint = Paint()
..color = Colors.black.withOpacity(0.04)
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 12);
canvas.drawPath(path, ambientShadowPaint);
// Fill background
final fillPaint = Paint()
..style = PaintingStyle.fill
..color = const Color(0xffFFFBFB);
canvas.drawPath(path, fillPaint);
// Border stroke
final strokePaint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 0.8
..color = const Color(0xffE5E5E5);
canvas.drawPath(path, strokePaint);
// 🔹 Dotted perforation line
final dashPaint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 1
..color = const Color(0xff787878);
const double dashWidth = 4;
const double dashSpace = 4;
double startX = 12;
final double endX = size.width - 12;
while (startX < endX) {
final double currentEnd = (startX + dashWidth).clamp(0, endX);
canvas.drawLine(
Offset(startX, dividerY),
Offset(currentEnd, dividerY),
dashPaint,
);
startX += dashWidth + dashSpace;
}
}
@override
bool shouldRepaint(covariant _TicketBackgroundPainter oldDelegate) {
return cornerRadius != oldDelegate.cornerRadius ||
notchRadius != oldDelegate.notchRadius ||
dividerY != oldDelegate.dividerY ||
borderColor != oldDelegate.borderColor ||
shadowColor != oldDelegate.shadowColor;
}
}

View File

@@ -0,0 +1,90 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:google_fonts/google_fonts.dart';
class QrContainerWidget extends StatelessWidget {
final String qrImagePath;
final String cityCardTitle;
final String qrCode;
final String cardType;
const QrContainerWidget({
super.key,
required this.qrImagePath,
required this.cityCardTitle,
required this.qrCode,
required this.cardType
});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
height: 380.h,
margin: EdgeInsets.symmetric(horizontal: 20.w),
padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 20.h),
decoration: BoxDecoration(
color: const Color(0xffF95F62).withOpacity(0.1),
borderRadius: BorderRadius.circular(14.r),
border: Border.all(color: cardType.toLowerCase() == "unlimited card" ? const Color(0xffF95F62) : const Color(0xffF95FAF), width: 2),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
cityCardTitle,
style: GoogleFonts.poppins(
fontSize: 18.sp,
color: cardType.toLowerCase() == "unlimited card" ? const Color(0xffF95F62) : const Color(0xffF95FAF),
fontWeight: FontWeight.w500,
),
),
Image.asset(
qrImagePath,
height: 250.h,
width: 250.w,
fit: BoxFit.contain,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
qrCode,
style: GoogleFonts.poppins(
fontWeight: FontWeight.w500,
fontSize: 15.sp,
color: Color(0xff212121)
),
),
SizedBox(width: 6.w),
GestureDetector(
onTap: () async {
await Clipboard.setData(ClipboardData(text: qrCode));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
"Code copied to clipboard!",
style: GoogleFonts.poppins(
color: Colors.white,
fontSize: 12.sp,
),
),
backgroundColor: Colors.black87,
behavior: SnackBarBehavior.floating,
duration: const Duration(seconds: 2),
),
);
},
child: const Icon(Icons.copy, size: 18, color: Color(0xff212121)),
),
],
),
],
),
);
}
}

View File

@@ -6,6 +6,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../common_packages/app_bar.dart';
import '../blocs/postcard_creation_bloc.dart';
import '../blocs/postcard_creation_state.dart';
import '../widgets/message_card_widget.dart';
import '../widgets/postcard_preview_widget.dart';
class OrderPostcardPreviewPageView extends StatefulWidget {
@@ -138,13 +139,9 @@ class _OrderPostcardPreviewPageViewState extends State<OrderPostcardPreviewPageV
),
showImage ?
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.asset(
"assets/images/post_card_intro.png",
width: double.infinity,
fit: BoxFit.cover,
),
MessageCardWidget(
message: state.message ?? "",
selectedFont: state.selectedFont,
):
PostCardPreviewWidget(
imagePath: state.imagePath ?? "",

View File

@@ -7,6 +7,7 @@ import 'package:google_fonts/google_fonts.dart';
import '../../common_packages/app_bar.dart';
import '../blocs/postcard_creation_bloc.dart';
import '../blocs/postcard_creation_state.dart';
import '../widgets/message_card_widget.dart';
import '../widgets/postcard_preview_widget.dart';
class OrderSuccessPageView extends StatelessWidget {
@@ -51,7 +52,9 @@ class OrderSuccessPageView extends StatelessWidget {
color: const Color(0xff585858),
),
children: const [
TextSpan(text: "Your order has been placed. Your order\nid is "),
TextSpan(
text: "Your order has been placed. Your order\nid is ",
),
TextSpan(
text: "#AG74563",
style: TextStyle(
@@ -75,10 +78,21 @@ class OrderSuccessPageView extends StatelessWidget {
const SizedBox(height: 28),
Container(
padding: EdgeInsets.fromLTRB(30, 10, 30, 10),
Padding(
padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 30),
child: Transform.rotate(
angle: 0.08,
angle: 0.20,
child: MessageCardWidget(
message: state.message ?? "",
selectedFont: state.selectedFont,
),
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 30),
child: Transform.rotate(
angle: -0.15,
child: PostCardPreviewWidget(
imagePath: state.imagePath ?? "",
message: state.message ?? "",
@@ -88,8 +102,7 @@ class OrderSuccessPageView extends StatelessWidget {
),
const SizedBox(height: 20),
const SizedBox(height: 30),
SizedBox(
width: double.infinity,
@@ -122,4 +135,3 @@ class OrderSuccessPageView extends StatelessWidget {
);
}
}

View File

@@ -6,6 +6,7 @@ import '../../common_packages/app_bar.dart';
import '../blocs/postcard_creation_bloc.dart';
import '../blocs/postcard_creation_events.dart';
import '../blocs/postcard_creation_state.dart';
import '../widgets/message_card_widget.dart';
import '../widgets/postcard_preview_widget.dart';
class PostcardCheckoutPageView extends StatelessWidget {
@@ -53,7 +54,11 @@ class PostcardCheckoutPageView extends StatelessWidget {
const SizedBox(height: 16),
MessageCardWidget(
message: state.message ?? "",
selectedFont: state.selectedFont,
),
SizedBox(height: 10.h),
PostCardPreviewWidget(
imagePath: state.imagePath ?? "",
message: state.message ?? "",
@@ -76,24 +81,20 @@ class PostcardCheckoutPageView extends StatelessWidget {
_buildPaymentRow("Subtotal", "\$ 50"),
const SizedBox(height: 20),
_buildPaymentRow(
"Discount",
"\$ 20",
highlight: true,
),
_buildPaymentRow("Discount", "\$ 20", highlight: true),
const SizedBox(height: 8),
Divider(color: Colors.black),
_buildPaymentRow("Grand Total", "\$ 30", size: 20.sp),
const SizedBox(height: 28),
Container(
color: Color(0xffFAFAFA),
height: 10,
),
Container(color: Color(0xffFAFAFA), height: 10),
const SizedBox(height: 10),
Row(
children: [
const Icon(Icons.home_outlined,
color: Color(0xffF95F62), size: 20),
const Icon(
Icons.home_outlined,
color: Color(0xffF95F62),
size: 20,
),
const SizedBox(width: 10),
Expanded(
child: Text(
@@ -106,19 +107,17 @@ class PostcardCheckoutPageView extends StatelessWidget {
),
),
IconButton(
onPressed: () {
},
icon: const Icon(Icons.edit_outlined,
color: Color(0xffF95F62), size: 18),
onPressed: () {},
icon: const Icon(
Icons.edit_outlined,
color: Color(0xffF95F62),
size: 18,
),
),
],
),
const SizedBox(height: 10),
Container(
color: Color(0xffFAFAFA),
height: 10,
),
Container(color: Color(0xffFAFAFA), height: 10),
const SizedBox(height: 40),
@@ -155,8 +154,12 @@ class PostcardCheckoutPageView extends StatelessWidget {
}
/// 💵 Helper for payment summary row
Widget _buildPaymentRow(String label, String value,
{bool highlight = false, double? size}) {
Widget _buildPaymentRow(
String label,
String value, {
bool highlight = false,
double? size,
}) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@@ -173,11 +176,13 @@ class PostcardCheckoutPageView extends StatelessWidget {
? BoxDecoration(
color: const Color(0xffFDCDCE),
borderRadius: BorderRadius.circular(6),
border: Border.all(color: Color(0xffEDEDED))
border: Border.all(color: Color(0xffEDEDED)),
)
: null,
padding:
EdgeInsets.symmetric(horizontal: highlight ? 6 : 0, vertical: 2),
padding: EdgeInsets.symmetric(
horizontal: highlight ? 6 : 0,
vertical: 2,
),
child: Text(
value,
style: GoogleFonts.poppins(
@@ -191,4 +196,3 @@ class PostcardCheckoutPageView extends StatelessWidget {
);
}
}

View File

@@ -8,6 +8,7 @@ import 'package:google_fonts/google_fonts.dart';
import '../../common_packages/app_bar.dart';
import '../blocs/postcard_creation_bloc.dart';
import '../blocs/postcard_creation_state.dart';
import '../widgets/message_card_widget.dart';
import '../widgets/postcard_preview_widget.dart';
import '../widgets/purchase_details_bottom_sheet.dart';
import '../widgets/step_progressbar.dart';
@@ -47,25 +48,6 @@ class _PreviewPostcardStepPageViewState extends State<PreviewPostcardStepPageVie
),
const SizedBox(height: 20),
showImage ?
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.asset(
"assets/images/post_card_intro.png",
width: double.infinity,
fit: BoxFit.cover,
),
):
PostCardPreviewWidget(
imagePath: state.imagePath ?? "",
message: state.message ?? "",
selectedFont: state.selectedFont,
),
const SizedBox(height: 24),
// 🔁 Flip Buttons
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@@ -109,7 +91,19 @@ class _PreviewPostcardStepPageViewState extends State<PreviewPostcardStepPageVie
const SizedBox(height: 16),
// ▶ Next Button
showImage ?
MessageCardWidget(
message: state.message ?? "",
selectedFont: state.selectedFont,
):
PostCardPreviewWidget(
imagePath: state.imagePath ?? "",
message: state.message ?? "",
selectedFont: state.selectedFont,
),
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
child: ElevatedButton(

View File

@@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
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});
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
children: [
Image.asset(
'assets/images/postcard_bg.png',
width: double.infinity,
fit: BoxFit.contain,
),
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,
),
),
),
),
],
);
}
}

View File

@@ -1,60 +1,37 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:google_fonts/google_fonts.dart';
class PostCardPreviewWidget extends StatelessWidget {
final String imagePath;
final String message;
final String? selectedFont;
const PostCardPreviewWidget({super.key, required this.imagePath, required this.message, this.selectedFont});
const PostCardPreviewWidget({
super.key,
required this.imagePath,
required this.message,
this.selectedFont,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(12),
width: double.infinity,
height: 230.h,
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: const Color(0xFFFFF5F5),
gradient: LinearGradient(colors: [
Color(0xffE2D6C2),
Color(0xffFFF5E6),
Color(0xffFFF5E6),
]),
border: Border.all(
color: Color(0xff000000).withOpacity(0.12),
),
borderRadius: BorderRadius.circular(12),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Image.file(
File(imagePath),
height: 140.h,
width: 140.w,
fit: BoxFit.cover,
),
),
const SizedBox(height: 12),
CustomPaint(
painter: LinedPaperPainter(lineHeight: 28.0, topPadding: 38.0),
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 12,
),
child: Text(
message ?? "",
style: TextStyle(
fontFamily: selectedFont ??
GoogleFonts.poppins().fontFamily,
fontSize: 16.sp,
color: const Color(0xff1A1A1A),
height: 1.6,
),
),
),
),
],
),
child: Image.file(File(imagePath), fit: BoxFit.cover),
);
}
}
@@ -81,4 +58,3 @@ class LinedPaperPainter extends CustomPainter {
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

View File

@@ -172,7 +172,32 @@ class PurchaseDetailsBottomSheet {
),
),
const SizedBox(height: 32),
const SizedBox(height: 15),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
PurchaseDetailsBottomSheet.close(context);
bloc.add(GoToNextStep());
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xffF95F62),
padding: EdgeInsets.symmetric(vertical: 16.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(40),
),
),
child: Text(
"Proceed",
style: TextStyle(
color: Colors.white,
fontSize: 14.sp,
fontWeight: FontWeight.w600,
),
),
),
),
const SizedBox(height: 15),
],
),
);

View File

@@ -1,6 +1,14 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
ansicolor:
dependency: transitive
description:
name: ansicolor
sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f"
url: "https://pub.dev"
source: hosted
version: "2.0.3"
archive:
dependency: transitive
description:
@@ -105,6 +113,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.7.11"
equatable:
dependency: "direct main"
description:
name: equatable
sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7"
url: "https://pub.dev"
source: hosted
version: "2.0.7"
fake_async:
dependency: transitive
description:
@@ -182,6 +198,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "5.0.0"
flutter_native_splash:
dependency: "direct main"
description:
name: flutter_native_splash
sha256: "4fb9f4113350d3a80841ce05ebf1976a36de622af7d19aca0ca9a9911c7ff002"
url: "https://pub.dev"
source: hosted
version: "2.4.7"
flutter_otp_text_field:
dependency: "direct main"
description:
@@ -640,6 +664,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.0"
simple_gesture_detector:
dependency: transitive
description:
name: simple_gesture_detector
sha256: ba2cd5af24ff20a0b8d609cec3f40e5b0744d2a71804a2616ae086b9c19d19a3
url: "https://pub.dev"
source: hosted
version: "0.2.1"
sky_engine:
dependency: transitive
description: flutter
@@ -693,6 +725,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.1"
table_calendar:
dependency: "direct main"
description:
name: table_calendar
sha256: "0c0c6219878b363a2d5f40c7afb159d845f253d061dc3c822aa0d5fe0f721982"
url: "https://pub.dev"
source: hosted
version: "3.2.0"
term_glyph:
dependency: transitive
description:
@@ -717,6 +757,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.0"
universal_io:
dependency: transitive
description:
name: universal_io
sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
uuid:
dependency: transitive
description:
@@ -773,6 +821,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.6.1"
yaml:
dependency: transitive
description:
name: yaml
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
url: "https://pub.dev"
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.9.0 <4.0.0"
flutter: ">=3.35.0"

View File

@@ -43,6 +43,9 @@ dependencies:
flutter_otp_text_field: ^1.5.1+1
google_maps_flutter: ^2.13.1
geolocator: ^14.0.2
equatable: ^2.0.7
table_calendar: ^3.2.0
flutter_native_splash: ^2.4.7
dev_dependencies:
flutter_test:
@@ -99,3 +102,10 @@ flutter:
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/to/font-from-package
flutter_native_splash:
color: "#F95F62" # Background color (your coral red)
image: assets/logo/logo_city_cards_white.png # Your splash logo
android_12:
color: "#F95F62"
image: assets/logo/logo_city_cards_white.png
web: false