import 'dart:io'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:image_picker/image_picker.dart'; import 'package:intl/intl.dart'; // for city casing import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../config/constants/app_constant.dart'; import '../config/constants/arabe.dart' as Language; import '../config/custom_text.dart'; import '../providers/authentification_provider.dart'; import '../providers/language_provider.dart'; import '../providers/public_provider.dart'; import '../providers/utils_singleton.dart'; import '../providers/utils_singleton.dart' as utill; import '../utils/colors.dart'; import '../widgets/custom_button.dart'; /*─────────────────────────────────────────────────────────────────────────────*/ class EditProfileScreen extends StatefulWidget { const EditProfileScreen({super.key}); @override State createState() => _EditProfileScreenState(); } /*─────────────────────────────────────────────────────────────────────────────*/ class _EditProfileScreenState extends State { /* controllers */ final nameController = TextEditingController(); final emailController = TextEditingController(); final addressController = TextEditingController(); /* check-boxes */ bool notificationFcm = false; bool notificationEmail = false; /* dropdowns */ final genderOptions = ['MALE', 'FEMALE']; String? selectedGender; String? selectedCity; late AuthProvider authProvider; /*───────────────── helpers ─────────────────*/ /// Turn “sousse” ⇒ “Sousse”, remove duplicates, sort. List _canonicalCityList(List raw) { final canon = raw .map((c) => toBeginningOfSentenceCase(c.trim().toLowerCase())!) .toSet() .toList() ..sort(); return canon; } /// If `serverCity` matches (case-insensitive) any item in `cities`, /// return the nicely-cased version; otherwise return the raw value. String? _canonicalizeCity(List cities, String? serverCity) { if (serverCity == null) return null; final lower = serverCity.trim().toLowerCase(); for (final c in cities) { if (c.toLowerCase() == lower) return c; } return serverCity; // keeps unknown city visible } /// Ensure the *current* city is present exactly once in the list. List _withCurrentCity(List cities, String? current) { if (current == null) return cities; final lowerCurrent = current.trim().toLowerCase(); final exists = cities.any((c) => c.trim().toLowerCase() == lowerCurrent); return exists ? cities : [current, ...cities]; } /*───────────────── lifecycle ─────────────────*/ @override void initState() { super.initState(); /* kick off city fetch (once) */ WidgetsBinding.instance.addPostFrameCallback((_) { Provider.of(context, listen: false).fetchtunisiancity(); }); authProvider = Provider.of(context, listen: false); /* preload form fields */ nameController.text = authProvider.profile?.name ?? ''; emailController.text = authProvider.profile?.email ?? ''; addressController.text = authProvider.profile?.address ?? ''; selectedCity = authProvider.profile?.city; selectedGender = genderOptions.contains(authProvider.profile?.gender) ? authProvider.profile?.gender : null; notificationFcm = authProvider.profile?.notificationFcm ?? false; notificationEmail = authProvider.profile?.notificationEmail ?? false; } /*───────────────── build ─────────────────*/ @override Widget build(BuildContext context) { final lang = context.watch(); final public = context.watch(); final authWatch = context.watch(); final baseCities = _canonicalCityList(public.cityNames); final cities = _withCurrentCity(baseCities, selectedCity); selectedCity = _canonicalizeCity(cities, selectedCity); final languageProvider = context.watch(); bool isArabic = languageProvider.currentLangCode == Language.ar; return Scaffold( backgroundColor: Colors.white, body: SafeArea(child: _content(lang, authWatch, cities, isArabic)), ); } File? _pickedImage; Future _pickImage() async { final picker = ImagePicker(); final pickedFile = await picker.pickImage(source: ImageSource.gallery); if (pickedFile != null) { final File imageFile = File(pickedFile.path); final prefs = await SharedPreferences.getInstance(); String token = prefs.getString('token')!; bool success = await utill.uploadProfileImage(imageFile, pickedFile.name, token); await Provider.of(context, listen: false).fetchProfile(); if (success) { setState(() { _pickedImage = imageFile; }); } else { print("Upload failed, not updating _pickedImage."); } } } /// Extracted for reuse Widget _content(LanguageProvider lang, AuthProvider watch, List cities, isArabic) { return Scaffold( backgroundColor: Colors.white, body: SingleChildScrollView( padding: const EdgeInsets.only(bottom: 80), // prevent button overlap child: Column( children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ GestureDetector( onTap: () { Navigator.pop(context); }, child: Container( width: 32, height: 32, margin: const EdgeInsets.symmetric(vertical: 12), decoration: BoxDecoration( color: AppNewColors.lightgray, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: AppNewColors.black.withOpacity(.1), blurRadius: 1, offset: const Offset(2, 1), ), ], ), child: const Icon( Icons.arrow_back, color: AppNewColors.black, size: 16, ), ), ), Expanded( child: Center( child: Txt( text: 'Edit Profile', style: TextStyle( color: AppNewColors.titleTextColor, fontSize: 18, fontFamily: isArabic ? "Readex_Pro" : 'Inter', fontWeight: FontWeight.w600, height: 1.23, ), ), ), ), const SizedBox(width: 32), ], ), ), // profile image Stack( alignment: Alignment.center, children: [ GestureDetector( onTap: _pickImage, child: ClipOval( child: SizedBox( width: MediaQuery.sizeOf(context).width * 0.3, height: MediaQuery.sizeOf(context).width * 0.3, child: _pickedImage != null ? Image.file(_pickedImage!, fit: BoxFit.cover) : CachedNetworkImage( imageUrl: '$mediaUrl${utils.user?.image?.url}', fit: BoxFit.fill, placeholder: (context, url) => const Center(child: CircularProgressIndicator()), errorWidget: (context, url, error) => Image.asset( "assets/images/wanislogo.png", fit: BoxFit.cover, ), ), ), ), ), IgnorePointer( child: Container( width: MediaQuery.sizeOf(context).width * 0.25, height: MediaQuery.sizeOf(context).width * 0.25, decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.black.withOpacity(0.1), ), child: Center( child: Icon(Icons.camera_alt, color: Colors.white.withOpacity(.8), size: 32), ), ), ), ], ), _gap(35), AddressInput(controller: nameController, label: 'Name'), AddressInput(controller: emailController, label: 'Email'), _cityDropdown(cities), AddressInput(controller: addressController, label: 'Address'), _genderDropdown(['MALE', 'FEMALE']), _gap(35), ], ), ), // 👇 Sticks the button at bottom bottomNavigationBar: Padding( padding: const EdgeInsets.all(16.0), child: _saveButton(lang, watch), ), ); } /*───────────────── widgets ─────────────────*/ Widget _cityDropdown(List cities) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 22.0, vertical: 10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Txt( text: 'City', style: TextStyle( color: AppNewColors.canvasColor, fontWeight: FontWeight.w500)), _gap(8), Container( height: 48, padding: const EdgeInsets.symmetric(horizontal: 12), decoration: _boxDecoration(), alignment: Alignment.centerLeft, child: cities.isEmpty ? const SizedBox( height: 24, width: 24, child: CircularProgressIndicator(strokeWidth: 2), ) : DropdownButtonHideUnderline( child: DropdownButton( isExpanded: true, borderRadius: BorderRadius.circular(12), dropdownColor: Colors.white, // background of opened list style: const TextStyle( fontSize: 14, color: Colors.black87, ), value: selectedCity, hint: const Txt(text: 'Select city'), items: cities .map((c) => DropdownMenuItem( value: c, child: Txt( text: c, style: const TextStyle(), ), )) .toList(), onChanged: (val) => setState(() => selectedCity = val), ), ), ), ], ), ); } Widget _genderDropdown(List gender) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 22.0, vertical: 10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Txt( text: 'Gender', style: TextStyle( color: AppNewColors.canvasColor, fontWeight: FontWeight.w500)), _gap(8), Container( height: 48, padding: const EdgeInsets.symmetric(horizontal: 12), decoration: _boxDecoration(), alignment: Alignment.centerLeft, child: gender.isEmpty ? const SizedBox( height: 24, width: 24, child: CircularProgressIndicator(strokeWidth: 2), ) : DropdownButtonHideUnderline( child: DropdownButton( isExpanded: true, borderRadius: BorderRadius.circular(12), dropdownColor: Colors.white, // background of opened list style: const TextStyle( fontSize: 14, color: Colors.black87, ), value: selectedGender, hint: const Txt(text: 'Select Gender'), items: gender .map((c) => DropdownMenuItem( value: c, child: Txt( text: c, style: const TextStyle(), ), )) .toList(), onChanged: (val) => setState(() => selectedGender = val), ), ), ), ], ), ); } Widget _genderChips() { return Padding( padding: const EdgeInsets.symmetric(horizontal: 22.0, vertical: 12), child: Row( children: [ _buildGenderTile('MALE', Icons.male), const SizedBox(width: 8), _buildGenderTile('FEMALE', Icons.female), ], ), ); } Widget _buildGenderTile(String value, IconData icon) { final active = selectedGender == value; return Expanded( child: InkWell( borderRadius: BorderRadius.circular(12), onTap: () => setState(() => selectedGender = value), child: Container( height: 48, decoration: BoxDecoration( color: active ? AppNewColors.primaryColor : Colors.white, border: Border.all(color: AppNewColors.primaryColor), borderRadius: BorderRadius.circular(12), boxShadow: const [ BoxShadow( color: Colors.black12, blurRadius: 6, offset: Offset(0, 2)) ], ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(icon, size: 18, color: active ? Colors.white : AppNewColors.primaryColor), const SizedBox(width: 6), Txt( text: value, style: TextStyle( color: active ? Colors.white : AppNewColors.canvasColor, fontWeight: FontWeight.w500, ), ), ], ), ), ), ); } Widget _notifications() { return Column( children: [ CheckboxListTile( title: const Txt( text: 'Notification', style: TextStyle( color: AppNewColors.canvasColor, fontWeight: FontWeight.w500)), activeColor: AppNewColors.primaryColor, value: notificationFcm, onChanged: (val) => setState(() => notificationFcm = val ?? false), ), CheckboxListTile( title: const Txt( text: 'Notification Email', style: TextStyle( color: AppNewColors.canvasColor, fontWeight: FontWeight.w500)), activeColor: AppNewColors.primaryColor, value: notificationEmail, onChanged: (val) => setState(() => notificationEmail = val ?? false), ), ], ); } Widget _saveButton(LanguageProvider lang, AuthProvider watch) { final isArabic = lang.currentLangCode == Language.ar; return CustomButton( title: isArabic ? 'تعديل الملف الشخصي' : 'Edit profile', width: MediaQuery.of(context).size.width * .7, height: 48, fontSize: 18, borderRadius: 12, backgroundColor: AppNewColors.primaryColor, borderColor: AppNewColors.primaryColor, isloading: watch.loader, loadercolor: AppNewColors.white, onClick: _saveProfile, ); } /*───────────────── save logic ─────────────────*/ Future _saveProfile() async { final updated = {}; void test(String key, dynamic oldVal, dynamic newVal) { if (newVal != null && newVal != oldVal) updated[key] = newVal; } test('name', authProvider.profile?.name, nameController.text); test('city', authProvider.profile?.city, selectedCity); test('email', authProvider.profile?.email, emailController.text); test('gender', authProvider.profile?.gender, selectedGender); test('address', authProvider.profile?.address, addressController.text); test('notificationFcm', authProvider.profile?.notificationFcm, notificationFcm); test('notificationEmail', authProvider.profile?.notificationEmail, notificationEmail); if (updated.isEmpty) return; await authProvider.updateProfile(updated); setState(() {}); // refresh UI } /*───────────────── small helpers ─────────────────*/ BoxDecoration _boxDecoration() => BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 6)], ); Widget _gap(double h) => SizedBox(height: h); } /*─────────────────────────────────────────────────────────────────────────────*/ class AddressInput extends StatelessWidget { const AddressInput( {super.key, required this.controller, required this.label}); final TextEditingController controller; final String label; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 22), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Txt(text: label, style: const TextStyle( color: AppNewColors.canvasColor, fontWeight: FontWeight.w500, )), const SizedBox(height: 8), Container( height: 48, decoration: BoxDecoration( color: AppNewColors.inputColor, borderRadius: BorderRadius.circular(12), // boxShadow: const [ // BoxShadow(color: Colors.black12, blurRadius: 6) // ], ), child: TextField( controller: controller, decoration: const InputDecoration( contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 14), border: InputBorder.none, ), ), ), const SizedBox(height: 8), ], ), ); } }