1212 lines
46 KiB
Dart
1212 lines
46 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:google_fonts/google_fonts.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:syncfusion_flutter_datepicker/datepicker.dart';
|
|
import '../viewmodels/recurring_block_viewmodel.dart';
|
|
|
|
class RecurringBlockPage extends StatelessWidget {
|
|
const RecurringBlockPage({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ChangeNotifierProvider(
|
|
create: (_) => RecurringBlockViewModel(),
|
|
child: Consumer<RecurringBlockViewModel>(
|
|
builder: (context, viewModel, _) {
|
|
final step = viewModel.currentStep;
|
|
return Scaffold(
|
|
backgroundColor: Colors.white,
|
|
body: SafeArea(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildHeader(context, viewModel),
|
|
const SizedBox(height: 42),
|
|
_buildProgressIndicator(step),
|
|
const SizedBox(height: 24),
|
|
|
|
Expanded(
|
|
child: AnimatedSwitcher(
|
|
duration: const Duration(milliseconds: 400),
|
|
transitionBuilder: (child, animation) {
|
|
final offsetAnimation = Tween<Offset>(
|
|
begin: const Offset(0.2, 0),
|
|
end: Offset.zero,
|
|
).animate(animation);
|
|
return SlideTransition(
|
|
position: offsetAnimation,
|
|
child: FadeTransition(
|
|
opacity: animation,
|
|
child: child,
|
|
),
|
|
);
|
|
},
|
|
|
|
// 👇 This prevents the default centering and pins content to the top
|
|
layoutBuilder: (currentChild, previousChildren) {
|
|
return Stack(
|
|
alignment: Alignment.topCenter,
|
|
children: <Widget>[
|
|
...previousChildren,
|
|
if (currentChild != null)
|
|
Positioned.fill(
|
|
child: Align(
|
|
alignment: Alignment.topCenter,
|
|
child: currentChild,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
|
|
// 👇 also top-align the active child itself
|
|
child: Align(
|
|
alignment: Alignment.topCenter,
|
|
child: _buildStep(step, viewModel),
|
|
),
|
|
),
|
|
),
|
|
step == 3
|
|
? Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 20,
|
|
vertical: 14,
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: OutlinedButton(
|
|
onPressed: () {},
|
|
style: OutlinedButton.styleFrom(
|
|
side: const BorderSide(
|
|
color: Color(0xffF95F62),
|
|
),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
padding: const EdgeInsets.symmetric(
|
|
vertical: 16,
|
|
),
|
|
),
|
|
child: Text(
|
|
"Cancel",
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 15,
|
|
color: const Color(0xffF95F62),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 10),
|
|
Expanded(
|
|
child: ElevatedButton(
|
|
onPressed: () {},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xffF95F62),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
padding: const EdgeInsets.symmetric(
|
|
vertical: 16,
|
|
),
|
|
),
|
|
child: Text(
|
|
"Create Block",
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 15,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
)
|
|
: Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 20,
|
|
vertical: 10,
|
|
),
|
|
child: SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton(
|
|
onPressed: viewModel.nextStep,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: const Color(0xffF95F62),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
padding: const EdgeInsets.symmetric(
|
|
vertical: 16,
|
|
),
|
|
),
|
|
child: Text(
|
|
"Next",
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 15,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildHeader(BuildContext context, RecurringBlockViewModel viewModel) {
|
|
return Column(
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.only(left: 20.0),
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
viewModel.previousStep(context);
|
|
},
|
|
child: CircleAvatar(
|
|
radius: 20,
|
|
backgroundColor: const Color(0xffF95F62),
|
|
child: const Icon(Icons.arrow_back, color: Colors.white),
|
|
),
|
|
),
|
|
),
|
|
Text(
|
|
"Recurring block",
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 32,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
SizedBox(width: 26),
|
|
],
|
|
),
|
|
SizedBox(height: 6),
|
|
Align(
|
|
alignment: Alignment.center,
|
|
child: Text(
|
|
viewModel.currentStep == 0
|
|
? "Basic Information"
|
|
: viewModel.currentStep == 1
|
|
? "Recurrence Pattern"
|
|
: viewModel.currentStep == 2
|
|
? "Time Slots"
|
|
: viewModel.currentStep == 3
|
|
? "Review & Confirm"
|
|
: "",
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w400,
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _buildProgressIndicator(int step) {
|
|
return Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: List.generate(4, (index) {
|
|
final isActive = index == step;
|
|
return AnimatedContainer(
|
|
duration: const Duration(milliseconds: 300),
|
|
margin: const EdgeInsets.symmetric(horizontal: 4),
|
|
width: 59,
|
|
height: 10,
|
|
decoration: BoxDecoration(
|
|
color: isActive ? const Color(0xffF95F62) : const Color(0xffFCE7E7),
|
|
borderRadius: BorderRadius.circular(6),
|
|
),
|
|
);
|
|
}),
|
|
);
|
|
}
|
|
|
|
Widget _buildStep(int step, RecurringBlockViewModel viewModel) {
|
|
switch (step) {
|
|
case 0:
|
|
return _buildBasicInfo();
|
|
case 1:
|
|
return _buildRecurrenceType();
|
|
case 2:
|
|
return _buildTimeSlots();
|
|
case 3:
|
|
return _buildReviewConfirm();
|
|
default:
|
|
return const SizedBox.shrink();
|
|
}
|
|
}
|
|
|
|
Widget _buildBasicInfo() {
|
|
final categories = [
|
|
"Tourist Attraction",
|
|
"Museum & Gallery",
|
|
"Outdoor Activity",
|
|
"Entertainment",
|
|
"Food Experience",
|
|
"Cultural Site",
|
|
];
|
|
final imagePath = [
|
|
"assets/recurring/culture.png",
|
|
"assets/recurring/Entertainment.png",
|
|
"assets/recurring/food.png",
|
|
"assets/recurring/musuem.png",
|
|
"assets/recurring/outdoor.png",
|
|
"assets/recurring/tourist.png",
|
|
];
|
|
return Consumer<RecurringBlockViewModel>(builder: (context, viewModel, _) {
|
|
return SingleChildScrollView(key: const ValueKey("basic_info"),
|
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
child: Column(crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text("Category", style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w600, fontSize: 24,),),
|
|
const SizedBox(height: 10),
|
|
GridView.builder(shrinkWrap: true,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
itemCount: categories.length,
|
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
|
crossAxisCount: 2,
|
|
crossAxisSpacing: 10,
|
|
mainAxisSpacing: 10,
|
|
childAspectRatio: 2.8,),
|
|
itemBuilder: (context, index) {
|
|
final category = categories[index];
|
|
final image = imagePath[index];
|
|
final isSelected = viewModel.selectedCategory == category;
|
|
return GestureDetector(
|
|
onTap: () => viewModel.selectCategory(category),
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 24),
|
|
decoration: BoxDecoration(color: isSelected ? const Color(
|
|
0xffFFEDE6) : Colors.white,
|
|
borderRadius: BorderRadius.circular(10),
|
|
border: Border.all(color: isSelected ? const Color(
|
|
0xffFFEDE6) : Colors.grey.shade300, width: 1.5,),),
|
|
child: Row(mainAxisAlignment: MainAxisAlignment.start,
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
Padding(padding: const EdgeInsets.only(right: 8.0),
|
|
child: Image.asset(image, width: 32, height: 32,),),
|
|
Expanded(child: Text(
|
|
category, textAlign: TextAlign.left,
|
|
maxLines: 2,
|
|
overflow: TextOverflow.visible,
|
|
style: GoogleFonts.poppins(fontSize: 16.0,
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.black,
|
|
height: 1.2,),),),
|
|
],),),);
|
|
},),
|
|
const SizedBox(height: 20),
|
|
_inputField("Attraction Name", "Enter attraction name"),
|
|
_inputField("Location", "Enter location"),
|
|
_inputField(
|
|
"Description (Optional)", "Brief description", maxLines: 3,),
|
|
const SizedBox(height: 12),
|
|
Row(children: [
|
|
Expanded(child: _dateField("Start Date")),
|
|
const SizedBox(width: 10),
|
|
Expanded(child: _dateField("End Date")),
|
|
],),
|
|
],),);
|
|
},);
|
|
}
|
|
|
|
// --- Step 1: Recurrence Type ---
|
|
Widget _buildRecurrenceType() {
|
|
final days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
|
|
return Consumer<RecurringBlockViewModel>(
|
|
builder: (context, viewModel, _) {
|
|
return SingleChildScrollView(
|
|
key: const ValueKey("recurrence_type"),
|
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Recurrence Type",
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 24,
|
|
),
|
|
),
|
|
const SizedBox(height: 10),
|
|
|
|
// Recurrence Options (Daily, Weekly, Monthly)
|
|
...["Daily", "Weekly", "Monthly"].map((type) {
|
|
final subtitle = type == "Daily"
|
|
? "Every Day"
|
|
: type == "Weekly"
|
|
? "Specific days of the week"
|
|
: "Same dates each month";
|
|
|
|
final isSelected = viewModel.selectedRecurrence == type;
|
|
|
|
return SingleChildScrollView(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
GestureDetector(
|
|
onTap: () => viewModel.selectRecurrence(type),
|
|
child: Container(
|
|
margin: const EdgeInsets.only(bottom: 12),
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: isSelected
|
|
? const Color(0xFFFFF1EE)
|
|
: Colors.white,
|
|
border: Border.all(
|
|
color: isSelected
|
|
? const Color(0xffF95F62)
|
|
: Colors.grey.shade300,
|
|
),
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Image.asset(
|
|
"assets/recurring/lets-icons_date-fill.png",
|
|
scale: 4,
|
|
),
|
|
const SizedBox(width: 12),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
type,
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w500,
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
Text(
|
|
subtitle,
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w400,
|
|
color: Colors.black54,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
// 🟩 WEEKLY SECTION (appears directly under Weekly card)
|
|
if (isSelected && type == "Weekly") ...[
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
"Select Days",
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 24,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Wrap(
|
|
spacing: 8,
|
|
runSpacing: 8,
|
|
children: days.map((day) {
|
|
final isDaySelected = viewModel.selectedDays
|
|
.contains(day);
|
|
return GestureDetector(
|
|
onTap: () => viewModel.toggleDay(day),
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 14,
|
|
vertical: 8,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: isDaySelected
|
|
? const Color(0xFFFFF1EE)
|
|
: Colors.white,
|
|
border: Border.all(
|
|
color: isDaySelected
|
|
? const Color(0xffF95F62)
|
|
: Colors.grey.shade400,
|
|
),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Text(
|
|
day,
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.w500,
|
|
color: isDaySelected
|
|
? const Color(0xffF95F62)
|
|
: Colors.black,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
const SizedBox(height: 16),
|
|
SizedBox(
|
|
child: Text(
|
|
"Every ${viewModel.repeatWeek} week(s)",
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 24,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 16,
|
|
vertical: 10,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xffF6F6F6),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
_roundButton(
|
|
Icons.remove,
|
|
viewModel.decrementWeek,
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 12.0,
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
"${viewModel.repeatWeek}",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
_roundButton(Icons.add, viewModel.incrementWeek),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
"repeats 1 ",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 13,
|
|
color: Colors.black54,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
],
|
|
|
|
// 🟦 MONTHLY SECTION (calendar starts immediately under Monthly card)
|
|
if (isSelected && type == "Monthly") ...[
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
"Select Dates",
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 24,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(12),
|
|
color: Colors.white,
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.grey.withOpacity(0.1),
|
|
spreadRadius: 1,
|
|
blurRadius: 2,
|
|
offset: const Offset(0, 1),
|
|
),
|
|
],
|
|
),
|
|
child: SfDateRangePicker(
|
|
// 🔴 Selection & Range Colors
|
|
startRangeSelectionColor: const Color(0xffF95F62),
|
|
endRangeSelectionColor: const Color(0xffF95F62),
|
|
rangeSelectionColor: const Color(0xFFFFF1EE),
|
|
selectionTextStyle: GoogleFonts.poppins(
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
selectionMode: DateRangePickerSelectionMode.range,
|
|
// 🧭 Navigation & Layout
|
|
backgroundColor: Colors.white,
|
|
showNavigationArrow: true,
|
|
initialDisplayDate: viewModel.focusedMonth,
|
|
toggleDaySelection: false,
|
|
todayHighlightColor: const Color(0xffF95F62),
|
|
minDate: DateTime(2020),
|
|
maxDate: DateTime(2030),
|
|
|
|
// 📅 Header (Month / Year)
|
|
headerStyle: DateRangePickerHeaderStyle(
|
|
backgroundColor: Colors.white,
|
|
textAlign: TextAlign.center,
|
|
textStyle: GoogleFonts.poppins(
|
|
color: Colors.black,
|
|
fontWeight: FontWeight.w700,
|
|
fontSize: 18,
|
|
),
|
|
),
|
|
|
|
// 📆 Month View Settings
|
|
monthViewSettings:
|
|
const DateRangePickerMonthViewSettings(
|
|
// firstDayOfWeek: 1,
|
|
viewHeaderStyle:
|
|
DateRangePickerViewHeaderStyle(
|
|
textStyle: TextStyle(
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 14,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
showTrailingAndLeadingDates: true,
|
|
),
|
|
|
|
// 🗓️ Customize Month Cells
|
|
monthCellStyle: DateRangePickerMonthCellStyle(
|
|
todayTextStyle: GoogleFonts.poppins(
|
|
color: const Color(0xffF95F62),
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
textStyle: GoogleFonts.poppins(
|
|
color: Colors.black,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
trailingDatesTextStyle: GoogleFonts.poppins(
|
|
color: Colors.grey.shade400,
|
|
fontWeight: FontWeight.w400,
|
|
),
|
|
leadingDatesTextStyle: GoogleFonts.poppins(
|
|
color: Colors.grey.shade400,
|
|
fontWeight: FontWeight.w400,
|
|
),
|
|
),
|
|
|
|
// 🔁 Handle Selections
|
|
onSelectionChanged: (args) {
|
|
if (args.value is PickerDateRange) {
|
|
viewModel.selectedDates.clear();
|
|
|
|
final PickerDateRange range = args.value;
|
|
final start = range.startDate;
|
|
final end = range.endDate ?? start;
|
|
|
|
// if (start != null && end != null) {
|
|
// for (var date = start;
|
|
// date.isBefore(end.add(const Duration(days: 1)));
|
|
// date = date.add(const Duration(days: 1))) {
|
|
// viewModel.selectedDates.add(date);
|
|
// }
|
|
// }
|
|
|
|
viewModel.notifyListeners();
|
|
} else if (args.value is DateTime) {
|
|
viewModel.selectedDates.clear();
|
|
viewModel.selectedDates.add(args.value);
|
|
viewModel.notifyListeners();
|
|
}
|
|
},
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
"Every ${viewModel.repeatWeek} Month(s)",
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 24,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 16,
|
|
vertical: 10,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xffF6F6F6),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
_roundButton(
|
|
Icons.remove,
|
|
viewModel.decrementWeek,
|
|
),
|
|
Center(
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 12.0,
|
|
),
|
|
child: Text(
|
|
"${viewModel.repeatWeek}",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
_roundButton(Icons.add, viewModel.incrementWeek),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
"repeats 1 Month",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 13,
|
|
color: Colors.black54,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
],
|
|
],
|
|
),
|
|
);
|
|
}),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _roundButton(IconData icon, VoidCallback onPressed) {
|
|
return GestureDetector(
|
|
onTap: onPressed,
|
|
child: Container(
|
|
width: 36,
|
|
height: 36,
|
|
decoration: const BoxDecoration(
|
|
color: Color(0xffF95F62),
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Icon(icon, color: Colors.white, size: 20),
|
|
),
|
|
);
|
|
}
|
|
|
|
// --- Step 2: Time Slots ---
|
|
Widget _buildTimeSlots() {
|
|
return Consumer<RecurringBlockViewModel>(
|
|
builder: (context, viewModel, _) {
|
|
return SingleChildScrollView(
|
|
key: const ValueKey("time_slots"),
|
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// ---------------- Title & Add Timing ----------------
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
"Time Slots Available",
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 20,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 14,
|
|
vertical: 6,
|
|
),
|
|
decoration: BoxDecoration(
|
|
border: Border.all(color: const Color(0xffF95F62)),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Text(
|
|
"+ Add timing",
|
|
style: GoogleFonts.poppins(
|
|
color: const Color(0xffF95F62),
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 14,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 20),
|
|
|
|
// ---------------- Slot Card ----------------
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xffF6F6F6),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Slot 1",
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w500,
|
|
fontSize: 16,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
const SizedBox(height: 10),
|
|
Row(
|
|
children: [
|
|
Expanded(child: _timeBox("Start Time", "9:00 AM")),
|
|
const SizedBox(width: 12),
|
|
Expanded(child: _timeBox("End Time", "12:00 PM")),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 24),
|
|
|
|
// ---------------- Capacity ----------------
|
|
Text(
|
|
"Capacity per Time Slot",
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w700,
|
|
fontSize: 20,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 20,
|
|
vertical: 16,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xffF6F6F6),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Expanded(child: _capacityButton(Icons.remove, () {})),
|
|
const SizedBox(width: 24),
|
|
Column(
|
|
children: [
|
|
Text(
|
|
"50",
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 18,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
Text(
|
|
"people",
|
|
style: GoogleFonts.poppins(
|
|
color: Colors.black45,
|
|
fontSize: 13,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(width: 24),
|
|
Expanded(child: _capacityButton(Icons.add, () {})),
|
|
],
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 20),
|
|
|
|
// ---------------- Create Slot Button ----------------
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: ElevatedButton(
|
|
onPressed: () {},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.black,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
|
),
|
|
child: Text(
|
|
"Create Slot",
|
|
style: GoogleFonts.poppins(
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 15,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 24),
|
|
Text(
|
|
"Preview",
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w700,
|
|
fontSize: 20,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xffEAF8EF),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
const CircleAvatar(
|
|
radius: 6,
|
|
backgroundColor: Color(0xff4CAF50),
|
|
),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
"The Enchanted Garden Adventure Park",
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 14,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 8),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 12,
|
|
vertical: 6,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
"9:00am - 12:00pm ",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 13,
|
|
color: Colors.black,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
color: Color(0xffF0F0F0),
|
|
borderRadius: BorderRadius.circular(6),
|
|
),
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 12.0,
|
|
vertical: 8,
|
|
),
|
|
child: Text(
|
|
"0/50",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 13,
|
|
color: Colors.black,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _inputField(String label, String hint, {int maxLines = 1}) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 16), child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(label, style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w700, fontSize: 24,),),
|
|
const SizedBox(height: 6),
|
|
TextField(
|
|
maxLines: maxLines, decoration: InputDecoration(
|
|
hintText: hint,
|
|
hintStyle: GoogleFonts.poppins(color: Colors.grey, fontSize: 13),
|
|
filled: true,
|
|
fillColor: const Color(0xffF6F6F6),
|
|
contentPadding: const EdgeInsets.symmetric(
|
|
horizontal: 12, vertical: 14,),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(10),
|
|
borderSide: BorderSide.none,),),),
|
|
],),);
|
|
}
|
|
|
|
Widget _dateField(String label) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(label, style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w600, fontSize: 24),),
|
|
const SizedBox(height: 6),
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 14),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xffF6F6F6),
|
|
borderRadius: BorderRadius.circular(10),),
|
|
child: Text("mm/dd/yyyy", style: GoogleFonts.poppins(fontSize: 14,
|
|
color: Colors.black87,
|
|
fontWeight: FontWeight.w600),),),
|
|
],);
|
|
}
|
|
}
|
|
|
|
// ---------------- Reusable UI Helpers ----------------
|
|
Widget _timeBox(String label, String value) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
label,
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.black54,
|
|
fontSize: 13,
|
|
),
|
|
),
|
|
const SizedBox(height: 6),
|
|
Container(
|
|
height: 48,
|
|
alignment: Alignment.centerLeft,
|
|
padding: const EdgeInsets.symmetric(horizontal: 14),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(8),
|
|
border: Border.all(color: Colors.grey.shade300),
|
|
),
|
|
child: Text(
|
|
value,
|
|
style: GoogleFonts.poppins(
|
|
color: Colors.black,
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 14,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget _capacityButton(IconData icon, VoidCallback onTap) {
|
|
return GestureDetector(
|
|
onTap: onTap,
|
|
child: Container(
|
|
width: 48,
|
|
height: 36,
|
|
alignment: Alignment.center,
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFFFF1EE),
|
|
border: Border.all(color: const Color(0xffF95F62)),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Icon(icon, color: const Color(0xffF95F62), size: 20),
|
|
),
|
|
);
|
|
}
|
|
|
|
// --- Step 3: Review & Confirm ---
|
|
Widget _buildReviewConfirm() {
|
|
return SingleChildScrollView(
|
|
key: const ValueKey("review_confirm"),
|
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Basic Information
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xffF6F6F6),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Basic Information",
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w500,
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
_reviewRow("Name:", "The Enchanted Garden"),
|
|
_reviewRow("Location:", "Dubai"),
|
|
_reviewRow("Category:", "Tourist Attraction"),
|
|
_reviewRow("Duration:", "01/02/2025 to 02/02/2025"),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// Schedule Information
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xffF6F6F6),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Schedule Information",
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w500,
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
_reviewRow("Pattern:", "Weekly"),
|
|
_reviewRow("Days:", "Sun, Mon, Tues"),
|
|
_reviewRow("Time Slots:", "1 slot"),
|
|
_reviewRow("Capacity:", "50 people per slot"),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// Preview Section
|
|
Container(
|
|
width: double.infinity,
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xffF6F6F6),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Preview (First 10 days)",
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w500,
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
Wrap(
|
|
spacing: 8,
|
|
runSpacing: 8,
|
|
children:
|
|
[
|
|
"Dec 4",
|
|
"Dec 7",
|
|
"Dec 8",
|
|
"Dec 9",
|
|
"Dec 10",
|
|
"Dec 11",
|
|
"Dec 12",
|
|
"Dec 13",
|
|
"Dec 14",
|
|
"Dec 15",
|
|
]
|
|
.map(
|
|
(date) =>
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 14,
|
|
vertical: 8,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
border: Border.all(color: Colors.grey.shade300),
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
child: Text(
|
|
date,
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
),
|
|
)
|
|
.toList(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 30),
|
|
|
|
// Bottom Buttons (Cancel / Create Block)
|
|
const SizedBox(height: 20),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _reviewRow(String label, String value) {
|
|
return Padding(
|
|
padding: const EdgeInsets.only(bottom: 6),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
SizedBox(
|
|
width: 90,
|
|
child: Text(
|
|
label,
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w400,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: Text(
|
|
value,
|
|
style: GoogleFonts.poppins(
|
|
fontWeight: FontWeight.w400,
|
|
fontSize: 14,
|
|
color: Color(0xff767676),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|