import 'dart:io'; import 'package:business_panel/core/config/app_colors.dart'; import 'package:business_panel/domain/entities/discount_type_entity.dart'; import 'package:business_panel/gen/assets.gen.dart'; import 'package:business_panel/presentation/discount/bloc/discount_bloc.dart'; import 'package:business_panel/presentation/discount/bloc/discount_event.dart'; import 'package:business_panel/presentation/discount/bloc/discount_state.dart'; import 'package:business_panel/presentation/widgets/custom_app_bar.dart'; import 'package:business_panel/presentation/widgets/info_popup.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:image_picker/image_picker.dart'; import 'package:persian_datetime_picker/persian_datetime_picker.dart'; class AddDiscountPage extends StatelessWidget { final String? discountId; const AddDiscountPage({super.key, this.discountId}); @override Widget build(BuildContext context) { return BlocProvider( create: (context) { final bloc = DiscountBloc(); if (discountId != null) { bloc.add(FetchDiscountDetails(discountId!)); } return bloc; }, child: _AddDiscountView(discountId: discountId), ); } } class _AddDiscountView extends StatefulWidget { final String? discountId; const _AddDiscountView({this.discountId}); @override State<_AddDiscountView> createState() => _AddDiscountViewState(); } class _AddDiscountViewState extends State<_AddDiscountView> { final _nameController = TextEditingController(); final _descController = TextEditingController(); final _priceController = TextEditingController(); final _discountPriceController = TextEditingController(); bool get _isEditMode => widget.discountId != null; @override void dispose() { _nameController.dispose(); _descController.dispose(); _priceController.dispose(); _discountPriceController.dispose(); super.dispose(); } Future _pickValidityDates(BuildContext context) async { Jalali? startDate = await showPersianDatePicker( context: context, initialDate: Jalali.now(), firstDate: Jalali.now(), lastDate: Jalali(1500), ); if (startDate == null || !context.mounted) return; TimeOfDay? startTime = await showTimePicker( context: context, initialTime: TimeOfDay.now(), ); if (startTime == null || !context.mounted) return; Jalali? endDate = await showPersianDatePicker( context: context, initialDate: startDate, firstDate: startDate, lastDate: Jalali(1500), ); if (endDate == null || !context.mounted) return; TimeOfDay? endTime = await showTimePicker( context: context, initialTime: startTime, ); if (endTime == null || !context.mounted) return; final DateTime startDateTime = startDate.toDateTime().add( Duration(hours: startTime.hour, minutes: startTime.minute), ); final DateTime endDateTime = endDate.toDateTime().add( Duration(hours: endTime.hour, minutes: endTime.minute), ); context.read().add( ValidityDateChanged(startDate: startDateTime, endDate: endDateTime), ); } final List discountTypes = [ DiscountTypeEntity(id: "dda9aef3-367e-48b3-ba35-8e7744d99659", name: "ساعت خوش"), DiscountTypeEntity(id: "57577fac-7d06-4b2e-a577-7d2ce98fee58", name: "رفیق بازی"), DiscountTypeEntity(id: "06156635-048b-4ed9-b5d5-2f89824435e1", name: "محصول جانبی رایگان"), DiscountTypeEntity(id: "16e7d1e9-29c4-4320-9869-3c986cc20734", name: "کالای مکمل"), DiscountTypeEntity(id: "fb600fbb-bab4-4e63-a25b-d8ffdacb7c09", name: "پلکانی"), DiscountTypeEntity(id: "488ef29e-415d-4362-b984-509faabac058", name: "دعوت نامه طلایی"), DiscountTypeEntity(id: "e03e5823-27d8-4f45-bd6c-f7c11822ec7a", name: "بازگشت وجه"), DiscountTypeEntity(id: "bb0eea57-b630-4373-baff-72df72921e67", name: "سایر"), ]; @override Widget build(BuildContext context) { return Scaffold( appBar: CustomAppBar(), body: BlocConsumer( listener: (context, state) { if (state.isSuccess) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( "تخفیف با موفقیت ${_isEditMode ? 'ویرایش' : 'ثبت'} شد!"), backgroundColor: Colors.green), ); Navigator.of(context).pop(true); } if (state.errorMessage != null && !state.isLoadingDetails) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(state.errorMessage!), backgroundColor: Colors.red), ); } if (state.productName.isNotEmpty && _nameController.text.isEmpty) { _nameController.text = state.productName; _descController.text = state.description; _priceController.text = state.price; _discountPriceController.text = state.discountedPrice; } }, builder: (context, state) { if (state.isLoadingDetails) { return const Center(child: CircularProgressIndicator()); } return SingleChildScrollView( padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _isEditMode ? "ویرایش تخفیف" : "تعریف تخفیف جدید", style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 20), ), const SizedBox(height: 24), _buildSectionTitle( title: "بارگذاری عکس از محصول", popupTitle: "یه عکس خوب، یه فروش خوب‌تر!", isMandatory: !_isEditMode, infoText: "عکس واضح، باکیفیت و واقعی از محصولت بذار. ترجیحا از عکس‌های اینترنتی یا تبلیغاتی استفاده نکن.", iconPath: Assets.icons.camera, ), const SizedBox(height: 16), _buildImagePickers(), const SizedBox(height: 30), _buildTextField( controller: _nameController, label: "نام محصول", isRequired: true, hint: "وافل شکلات فندقی", onChanged: (value) => context.read().add(ProductNameChanged(value)), ), const SizedBox(height: 30), _buildDiscountTypeDropdown(state), // Pass state here const SizedBox(height: 30), _buildTextField( controller: _descController, label: "توضیح برای تخفیف", hint: "مثلاً عصرونه، با ۵٪ تخفیف مهمون ما باش! ", isRequired: true, maxLines: 4, maxLength: 200, onChanged: (value) => context.read().add(DescriptionChanged(value)), ), const SizedBox(height: 30), _buildDateTimePicker(), const SizedBox(height: 30), _buildTimeRangePicker(context), const SizedBox(height: 30), _buildTextField( controller: _priceController, label: "قیمت بدون تخفیف", isRequired: true, hint: "مثلاً 240000 تومان", keyboardType: TextInputType.number, onChanged: (value) => context.read().add(PriceChanged(value)), ), const SizedBox(height: 30), _buildTextField( controller: _discountPriceController, label: "قیمت با تخفیف", hint: "مثلاً 200000 تومان", isRequired: true, keyboardType: TextInputType.number, onChanged: (value) => context .read() .add(DiscountedPriceChanged(value)), ), const SizedBox(height: 30), _buildNotificationRadiusSlider(), const SizedBox(height: 30), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: state.isSubmitting ? null : () { if (_isEditMode) { context .read() .add(UpdateDiscount(widget.discountId!)); } else { context.read().add(SubmitDiscount()); } }, child: state.isSubmitting ? const CircularProgressIndicator(color: Colors.white) : Text(_isEditMode ? "ثبت تغییرات" : "ثبت تخفیف"), ), ), const SizedBox(height: 30), ], ), ); }, ), ); } Widget _buildSectionTitle({ required String title, String? popupTitle, bool isMandatory = false, String? infoText, String? iconPath, }) { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ if (infoText != null && iconPath != null) IconButton( onPressed: () => showInfoDialog( context, title: popupTitle ?? title, content: infoText, iconPath: iconPath, ), icon: SvgPicture.asset(Assets.icons.infoCircle, width: 17), ), Text( title, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14), ), if (isMandatory) const Text(' *', style: TextStyle(color: Colors.red, fontSize: 17)), ], ); } Widget _buildImagePickers() { return BlocBuilder( buildWhen: (p, c) => p.productImages != c.productImages, builder: (context, state) { // We ensure the list has at least 2 elements for the UI, filling with null final displayImages = List?>.from(state.productImages); while (displayImages.length < 2) { displayImages.add(null); } return Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: List.generate(2, (index) { // *** CHANGE IS HERE: Read from the map structure *** final imageMap = displayImages[index]; final imageUrl = imageMap?['url']; final isUrl = imageUrl?.startsWith('http') ?? false; return GestureDetector( onTap: () async { final ImagePicker picker = ImagePicker(); final XFile? image = await picker.pickImage(source: ImageSource.gallery, imageQuality: 80); if (image != null && context.mounted) { context .read() .add(ProductImageAdded(image.path, index)); } }, child: Container( width: 125, height: 125, decoration: BoxDecoration( color: AppColors.uploadElevated, borderRadius: BorderRadius.circular(12), border: Border.all(color: AppColors.uploadElevated), image: imageUrl != null ? DecorationImage( image: isUrl ? NetworkImage(imageUrl) : FileImage(File(imageUrl)) as ImageProvider, fit: BoxFit.cover, ) : null, ), child: imageUrl == null ? Center( child: SvgPicture.asset( Assets.icons.addPic, width: 60, ), ) : null, ), ); }), ); }, ); } Widget _buildDiscountTypeDropdown(DiscountState state) { // Create a set of available IDs for quick lookup. final availableTypeIds = discountTypes.map((type) => type.id).toSet(); // Check if the current discount's type ID is in our list. If not, use null. final String? selectedValue = availableTypeIds.contains(state.discountTypeId) ? state.discountTypeId : null; return DropdownButtonFormField( value: selectedValue, // Use the safe value here. icon: SvgPicture.asset( Assets.icons.arrowDown, width: 24, color: Colors.black, ), menuMaxHeight: 400, hint: const Text("نوع تخفیف را انتخاب کنید"), decoration: _inputDecoration("نوع تخفیف", isRequired: true).copyWith( contentPadding: const EdgeInsets.symmetric( vertical: 14, horizontal: 20, ), ), borderRadius: BorderRadius.circular(12.0), items: discountTypes.map((type) { return DropdownMenuItem(value: type.id, child: Text(type.name)); }).toList(), onChanged: (value) { if (value != null) { context.read().add(DiscountTypeChanged(value)); } }, ); } Widget _buildDateTimePicker() { return BlocBuilder( buildWhen: (previous, current) => previous.startDate != current.startDate || previous.endDate != current.endDate, builder: (context, state) { String displayText = "انتخاب تاریخ"; if (state.startDate != null && state.endDate != null) { final jalaliStart = DateTimeExtensions(state.startDate!).toJalali(); final jalaliEnd = DateTimeExtensions(state.endDate!).toJalali(); final startFormatted = '${jalaliStart.year}/${jalaliStart.month.toString().padLeft(2, '0')}/${jalaliStart.day.toString().padLeft(2, '0')} - ${state.startDate!.hour.toString().padLeft(2, '0')}:${state.startDate!.minute.toString().padLeft(2, '0')}'; final endFormatted = '${jalaliEnd.year}/${jalaliEnd.month.toString().padLeft(2, '0')}/${jalaliEnd.day.toString().padLeft(2, '0')} - ${state.endDate!.hour.toString().padLeft(2, '0')}:${state.endDate!.minute.toString().padLeft(2, '0')}'; displayText = 'از $startFormatted\nتا $endFormatted'; } return InkWell( onTap: () => _pickValidityDates(context), child: InputDecorator( decoration: _inputDecoration( "تاریخ اعتبار تخفیف", isRequired: true, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( displayText, textDirection: TextDirection.rtl, style: const TextStyle(fontSize: 15), ), ), SvgPicture.asset(Assets.icons.calendarSearch), ], ), ), ); }, ); } Widget _buildTimeRangePicker(BuildContext context) { return BlocBuilder( buildWhen: (previous, current) => previous.startTime != current.startTime || previous.endTime != current.endTime, builder: (context, state) { String displayText = "انتخاب بازه زمانی"; if (state.startTime != null && state.endTime != null) { displayText = 'از ساعت ${state.startTime} تا ${state.endTime}'; } return InkWell( onTap: () async { final TimeOfDay? startTime = await showTimePicker(context: context, initialTime: TimeOfDay.now()); if (startTime == null) return; final TimeOfDay? endTime = await showTimePicker(context: context, initialTime: startTime); if (endTime == null) return; final formattedStartTime = '${startTime.hour.toString().padLeft(2, '0')}:${startTime.minute.toString().padLeft(2, '0')}'; final formattedEndTime = '${endTime.hour.toString().padLeft(2, '0')}:${endTime.minute.toString().padLeft(2, '0')}'; context.read().add( TimeRangeChanged( startTime: formattedStartTime, endTime: formattedEndTime), ); }, child: InputDecorator( decoration: _inputDecoration("بازه زمانی معتبر", isRequired: true), child: Text(displayText), ), ); }, ); } Widget _buildNotificationRadiusSlider() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ IconButton( onPressed: () => showInfoDialog( context, title: "انتخاب محدوده نمایش تخفیف", content: "محدوده‌ای رو مشخص کن که تخفیف‌هات فقط به کاربرانی که تو اون شعاع هستن نشون داده بشه.", iconPath: Assets.icons.radar2, ), icon: SvgPicture.asset(Assets.icons.infoCircle, width: 17), ), const Text( "شعاع ارسال اعلان تخفیف به مشتری‌ها", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14), ), ], ), ], ), BlocBuilder( builder: (context, state) { return Column( children: [ SliderTheme( data: SliderTheme.of(context).copyWith( activeTrackColor: AppColors.active, inactiveTrackColor: Colors.grey.shade300, trackShape: const RoundedRectSliderTrackShape(), trackHeight: 4.0, thumbColor: AppColors.active, thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 12.0), overlayColor: AppColors.active.withAlpha(32), overlayShape: const RoundSliderOverlayShape(overlayRadius: 28.0), ), child: Slider( value: state.notificationRadius, min: 0, max: 1000, divisions: 100, label: '${state.notificationRadius.toInt()} متر', onChanged: (value) { context .read() .add(NotificationRadiusChanged(value)); }, ), ), const SizedBox(height: 7), Text( '${state.notificationRadius.toInt()} متر', style: const TextStyle( fontWeight: FontWeight.normal, fontSize: 14, color: Colors.black, ), ), ], ); }, ), ], ); } Widget _buildTextField({ required String label, String? hint, bool isRequired = false, int? maxLines, int? maxLength, TextInputType? keyboardType, required TextEditingController controller, ValueChanged? onChanged, }) { return ValueListenableBuilder( valueListenable: controller, builder: (context, value, child) { return TextFormField( controller: controller, onChanged: onChanged, maxLines: maxLines, maxLength: maxLength, keyboardType: keyboardType, decoration: _inputDecoration(label, hint: hint, isRequired: isRequired) .copyWith( counterText: '', counter: maxLength != null ? Text( '${value.text.length}/$maxLength', style: Theme.of(context).textTheme.bodySmall, ) : null, ), ); }, ); } InputDecoration _inputDecoration(String label, {String? hint, bool isRequired = false}) { return InputDecoration( hintText: hint, hintStyle: const TextStyle(color: Color.fromARGB(255, 95, 95, 95), fontSize: 14), label: RichText( text: TextSpan( text: label, style: const TextStyle( color: Colors.black, fontFamily: 'Dana', fontSize: 18, fontWeight: FontWeight.bold, ), children: [ if (isRequired) const TextSpan( text: ' *', style: TextStyle(color: Colors.red)), ], ), ), ); } }