Completed the postcard section.

This commit is contained in:
2025-10-26 19:34:41 +05:30
parent 4956b9ea50
commit 9042cd57d5
16 changed files with 1270 additions and 89 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
assets/icons/edit_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/icons/send_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 882 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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