proxibuy_bussiness/lib/presentation/pages/store_info.dart

646 lines
24 KiB
Dart

import 'dart:io';
import 'package:business_panel/core/config/app_colors.dart';
import 'package:business_panel/domain/entities/category_entity.dart';
import 'package:business_panel/gen/assets.gen.dart';
import 'package:business_panel/presentation/pages/store_info_display_page.dart';
import 'package:business_panel/presentation/pages/working_hours_dialog.dart';
import 'package:business_panel/presentation/store_info/bloc/store_info_bloc.dart';
import 'package:business_panel/presentation/store_info/bloc/store_info_state.dart';
import 'package:business_panel/presentation/widgets/custom_app_bar.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; // **ADD THIS IMPORT**
import 'package:flutter_svg/flutter_svg.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:image_picker/image_picker.dart';
import 'package:business_panel/presentation/pages/osm_map_picker_page.dart';
import 'package:latlong2/latlong.dart';
class StoreInfoPage extends StatefulWidget {
const StoreInfoPage({super.key});
@override
State<StoreInfoPage> createState() => _StoreInfoPageState();
}
class _StoreInfoPageState extends State<StoreInfoPage> {
final _nameController = TextEditingController();
final _provinceController = TextEditingController();
final _cityController = TextEditingController();
final _addressController = TextEditingController();
final _plaqueController = TextEditingController();
final _postalCodeController = TextEditingController();
final _phoneController = TextEditingController();
final _licenseController = TextEditingController();
@override
void initState() {
super.initState();
final bloc = context.read<StoreInfoBloc>();
_nameController.text = bloc.state.storeName;
_provinceController.text = bloc.state.province;
_cityController.text = bloc.state.city;
_addressController.text = bloc.state.address;
_plaqueController.text = bloc.state.plaque;
_postalCodeController.text = bloc.state.postalCode;
_phoneController.text = bloc.state.contactPhone ?? '';
_licenseController.text = bloc.state.licenseNumber ?? '';
}
@override
void dispose() {
_nameController.dispose();
_provinceController.dispose();
_cityController.dispose();
_addressController.dispose();
_plaqueController.dispose();
_postalCodeController.dispose();
_phoneController.dispose();
_licenseController.dispose();
super.dispose();
}
Future<void> _pickImage(BuildContext context) async {
try {
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(
source: ImageSource.gallery,
imageQuality: 80,
);
if (image != null && context.mounted) {
context.read<StoreInfoBloc>().add(StoreLogoChanged(image.path));
}
} on PlatformException catch (e) {
print("خطای دسترسی یا پلتفرم: ${e.message}");
} catch (e) {
print("یک خطای ناشناخته رخ داد: $e");
}
}
Future<void> _showWorkingHoursDialog(BuildContext context) async {
final result = await showDialog<Map<String, dynamic>>(
context: context,
builder: (context) => const WorkingHoursDialog(),
);
if (result != null && context.mounted) {
context.read<StoreInfoBloc>().add(
WorkingScheduleChanged(
days: result['days'] as List<String>,
startTime: result['startTime'] as String,
endTime: result['endTime'] as String,
),
);
}
}
final List<CategoryEntity> activityTypes = [
CategoryEntity(id: "6803b940-3e19-48cd-9190-28d9f25421ff", name: "فست فود", emoji: "🍔🍕"),
CategoryEntity(id: "71e371f8-a47a-4a58-aee6-4ed0f26bf29b", name: "پوشاک", emoji: "👚👔"),
CategoryEntity(id: "42acff41-1165-4e62-89b9-58db7329ec3a", name: "تریا", emoji: "🍨🍹"),
CategoryEntity(id: "e33dd7f9-5b20-4273-8eea-59da6ca5f206", name: "لوازم دیجیتال", emoji: "📱📷"),
CategoryEntity(id: "b5881239-bfd5-4c27-967a-187316a7e0b7", name: "رستوران", emoji: "🍣🍢"),
CategoryEntity(id: "b73a868a-a2d2-4d96-8fd4-615327ed9629", name: "کافی شاپ", emoji: "☕🍰"),
CategoryEntity(id: "2f38918c-5566-4aec-a0a9-2c7c48b1e878", name: "کیف و کفش", emoji: "👜👞"),
CategoryEntity(id: "52c51010-3a63-4264-a350-e011c889f3dd", name: "سینما", emoji: "🎭🎟️"),
CategoryEntity(id: "34185954-f79f-4b9e-8eb2-1702679c40a0", name: "لوازم آرایشی", emoji: "💄💅️"),
CategoryEntity(id: "e4517b0c-aacf-4758-94bd-85f45062980f", name: "طلا و زیورآلات", emoji: "💍💎"),
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(),
body: BlocListener<StoreInfoBloc, StoreInfoState>(
listener: (context, state) {
if (state.isSuccess) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => BlocProvider.value(
value: BlocProvider.of<StoreInfoBloc>(context),
child: const StoreInfoDisplayPage(),
),
),
);
}
if (state.errorMessage != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(state.errorMessage!), backgroundColor: Colors.red),
);
context.read<StoreInfoBloc>().add(ClearStoreInfoError());
}
},
child: SingleChildScrollView(
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 16),
const Row(
children: [
Text(
"لوگوی فروشگاه",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20),
),
SizedBox(width: 5),
Text("(اختیاری)", style: TextStyle(color: Colors.black54)),
],
),
const SizedBox(height: 24),
Center(
child: GestureDetector(
onTap: () => _pickImage(context),
child: CircleAvatar(
radius: 65,
backgroundColor: AppColors.uploadElevated,
child: BlocBuilder<StoreInfoBloc, StoreInfoState>(
builder: (context, state) {
return Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: const Color.fromARGB(
255,
224,
224,
224,
).withOpacity(0.5),
spreadRadius: 1,
blurRadius: 40,
offset: const Offset(0, 10),
),
],
image:
state.logoPath != null
? DecorationImage(
image: FileImage(File(state.logoPath!)),
fit: BoxFit.cover,
)
: null,
),
child: Stack(
alignment: Alignment.center,
children: [
if(state.logoPath == null) SvgPicture.asset(Assets.icons.addImg),
Align(
alignment: Alignment.bottomRight,
child: CircleAvatar(
radius: 22,
backgroundColor: Colors.white,
child: CircleAvatar(
radius: 20,
backgroundColor: Colors.white,
child: SvgPicture.asset(Assets.icons.edit02),
),
),
),
],
),
);
},
),
),
),
),
const SizedBox(height: 32),
_buildSectionTitle(),
const SizedBox(height: 30),
_buildTextField(
label: "نام فروشگاه",
isRequired: true,
hint: "مثلاً کافه ایرونی",
controller: _nameController,
onChanged:
(value) => context.read<StoreInfoBloc>().add(
StoreNameChanged(value),
),
),
const SizedBox(height: 30),
_buildActivityTypeDropdown(context),
const SizedBox(height: 30),
Row(
children: [
Expanded(
child: _buildTextField(
label: "استان",
hint: "اصفهان",
controller: _provinceController,
onChanged:
(value) => context.read<StoreInfoBloc>().add(
ProvinceChanged(value),
),
),
),
const SizedBox(width: 16),
Expanded(
child: _buildTextField(
controller: _cityController,
label: "شهر",
hint: "اصفهان",
onChanged:
(value) => context.read<StoreInfoBloc>().add(
CityChanged(value),
),
),
),
],
),
const SizedBox(height: 30),
_buildTextField(
controller: _addressController,
label: "جزئیات آدرس",
isRequired: true,
maxLines: 3,
hint: "خیابان، محله، ساختمان و ....",
onChanged:
(value) =>
context.read<StoreInfoBloc>().add(AddressChanged(value)),
),
const SizedBox(height: 30),
_buildFeaturesSection(),
const SizedBox(height: 50),
Row(
children: [
Expanded(
child: _buildTextField(
controller: _plaqueController,
label: "پلاک",
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
onChanged:
(value) => context.read<StoreInfoBloc>().add(
PlaqueChanged(value),
),
),
),
const SizedBox(width: 16),
Expanded(
child: _buildTextField(
controller: _postalCodeController,
label: "کد پستی",
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
onChanged: (value) => context
.read<StoreInfoBloc>()
.add(PostalCodeChanged(value)),
),
),
],
),
const SizedBox(height: 30),
Center(
child: TextButton.icon(
onPressed: () async {
final result = await Navigator.of(context).push<LatLng?>(
MaterialPageRoute(
builder: (context) => const OsmMapPickerPage(),
),
);
if (result != null) {
context.read<StoreInfoBloc>().add(
StoreLocationChanged(
latitude: result.latitude,
longitude: result.longitude,
),
);
}
},
icon: SvgPicture.asset(
Assets.icons.map,
color: AppColors.button,
height: 23,
),
label: const Text(
"انتخاب آدرس فروشگاه روی نقشه *",
style: TextStyle(color: AppColors.button),
),
),
),
Center(
child: BlocBuilder<StoreInfoBloc, StoreInfoState>(
builder: (context, state) {
if (state.latitude != null && state.longitude != null) {
return Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
"آدرس با موفقیت ثبت شد ✓",
style: TextStyle(
color: Colors.green.shade700,
fontWeight: FontWeight.bold,
),
),
);
}
return const SizedBox.shrink();
},
),
),
const SizedBox(height: 30),
_buildTextField(
controller: _phoneController,
label: "تلفن تماس",
isRequired: true,
keyboardType: TextInputType.phone,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
hint: "شماره تماس ثابت یا موبایل فروشگاه",
onChanged: (value) =>
context.read<StoreInfoBloc>().add(ContactPhoneChanged(value)),
),
const SizedBox(height: 30),
_buildWorkingHoursPicker(context),
const SizedBox(height: 30),
_buildTextField(
controller: _licenseController,
label: "شماره جواز کسب",
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
hint: "شناسه صنفی 12 رقمی یکتا",
onChanged: (value) =>
context.read<StoreInfoBloc>().add(LicenseNumberChanged(value)),
),
const SizedBox(height: 44),
SizedBox(
width: double.infinity,
child: BlocBuilder<StoreInfoBloc, StoreInfoState>(
builder: (context, state) {
return ElevatedButton(
onPressed: state.isFormValid ? () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => BlocProvider.value(
value: BlocProvider.of<StoreInfoBloc>(context),
child: const StoreInfoDisplayPage(),
),
),
);
} : () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("لطفاً تمام فیلدهای ستاره‌دار را پر کنید."),
backgroundColor: Colors.red,
),
);
},
style: ElevatedButton.styleFrom(
backgroundColor: state.isFormValid ? AppColors.button : Colors.grey,
),
child: const Text("تایید و ادامه"),
);
},
),
),
const SizedBox(height: 34),
],
),
),
));
}
Widget _buildSectionTitle() {
return const Text(
"ثبت مشخصات",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
);
}
Widget _buildTextField({
required String label,
bool isRequired = false,
String? hint,
int maxLines = 1,
int? maxLength,
TextInputType? keyboardType,
TextEditingController? controller,
ValueChanged<String>? onChanged,
List<TextInputFormatter>? inputFormatters,
}) {
return TextFormField(
controller: controller,
onChanged: onChanged,
maxLines: maxLines,
maxLength: maxLength,
keyboardType: keyboardType,
inputFormatters: inputFormatters,
decoration: InputDecoration(
counterText: "",
hintText: hint,
hintStyle: const TextStyle(fontSize: 15, color: Colors.grey),
label: RichText(
text: TextSpan(
text: label,
style: const TextStyle(
color: Colors.black,
fontFamily: 'Dana',
fontSize: 19,
fontWeight: FontWeight.bold,
),
children: [
if (isRequired)
const TextSpan(
text: ' *',
style: TextStyle(color: Colors.red, fontSize: 16),
),
],
),
),
),
);
}
Widget _buildFeaturesSection() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"ویژگی‌های فروشگاه",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
const Text(
"حداکثر ۳ ویژگی برای معرفی بهتر فروشگاه خود اضافه کنید.",
style: TextStyle(color: Colors.grey, fontSize: 14),
),
const SizedBox(height: 16),
BlocBuilder<StoreInfoBloc, StoreInfoState>(
buildWhen: (previous, current) => previous.features != current.features,
builder: (context, state) {
return Column(
children: [
...state.features.asMap().entries.map((entry) {
final index = entry.key;
final controller = TextEditingController(text: entry.value)
..selection = TextSelection.fromPosition(
TextPosition(offset: entry.value.length),
);
return Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: Row(
children: [
Expanded(
child: _buildTextField(
controller: controller,
label: "ویژگی ${index + 1}",
hint: "مثلا: دارای ویوی شهر",
maxLength: 60,
onChanged: (value) {
context
.read<StoreInfoBloc>()
.add(StoreFeatureUpdated(index, value));
},
),
),
IconButton(
icon: const Icon(Icons.remove_circle_outline, color: Colors.red),
onPressed: () {
context
.read<StoreInfoBloc>()
.add(StoreFeatureRemoved(index));
},
),
],
),
);
}).toList(),
if (state.features.length < 3)
SizedBox(
width: double.infinity,
child: OutlinedButton.icon(
icon: const Icon(Icons.add),
label: const Text("افزودن ویژگی جدید"),
onPressed: () {
context.read<StoreInfoBloc>().add(StoreFeatureAdded());
},
),
),
],
);
},
),
],
);
}
Widget _buildWorkingHoursPicker(BuildContext context) {
return InkWell(
onTap: () => _showWorkingHoursDialog(context),
child: InputDecorator(
decoration: InputDecoration(
label: RichText(
text: const TextSpan(
text: "ساعت کار فروشگاه",
style: TextStyle(
color: Colors.black,
fontFamily: 'Dana',
fontSize: 19,
fontWeight: FontWeight.bold,
),
children: [
TextSpan(
text: ' *',
style: TextStyle(color: Colors.red, fontSize: 16),
),
],
),
),
contentPadding: const EdgeInsets.symmetric(
vertical: 18,
horizontal: 12,
),
),
child: BlocBuilder<StoreInfoBloc, StoreInfoState>(
buildWhen: (p, c) => p.workingDays != c.workingDays,
builder: (context, state) {
final hasData =
state.workingDays.isNotEmpty && state.startTime != null;
const Map<String, String> dayTranslations = {
'Saturday': 'شنبه',
'Sunday': 'یکشنبه',
'Monday': 'دوشنبه',
'Tuesday': 'سه‌شنبه',
'Wednesday': 'چهارشنبه',
'Thursday': 'پنج‌شنبه',
'Friday': 'جمعه',
};
final displayDays = state.workingDays
.map((day) => dayTranslations[day] ?? '')
.join('، ');
String displayText =
hasData
? "$displayDays\nاز ساعت ${state.startTime} تا ${state.endTime}"
: "انتخاب روز و ساعت کاری";
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
displayText,
style: TextStyle(
fontSize: 16,
color: hasData ? Colors.black : Colors.grey.shade600,
height: 1.5,
),
textAlign: TextAlign.right,
),
),
],
);
},
),
),
);
}
Widget _buildActivityTypeDropdown(BuildContext context) {
return Theme(
data: Theme.of(context).copyWith(canvasColor: const Color(0xFFF6F6F6)),
child: DropdownButtonFormField<String>(
value: context.watch<StoreInfoBloc>().state.activityTypeId,
icon: SvgPicture.asset(
Assets.icons.arrowDown,
width: 24,
color: Colors.black,
),
decoration: InputDecoration(
label: RichText(
text: const TextSpan(
text: "نوع فعالیت",
style: TextStyle(
color: Colors.black,
fontFamily: 'Dana',
fontSize: 19,
fontWeight: FontWeight.bold,
),
children: [
TextSpan(
text: ' *',
style: TextStyle(color: Colors.red, fontSize: 16),
),
],
),
),
),
borderRadius: BorderRadius.circular(12.0),
isExpanded: true,
items: activityTypes.map((CategoryEntity category) {
return DropdownMenuItem<String>(
value: category.id,
child: Text("${category.emoji} ${category.name}"),
);
}).toList(),
onChanged: (value) {
if (value != null) {
context.read<StoreInfoBloc>().add(ActivityTypeChanged(value));
}
},
),
);
}
}