import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; import 'package:real_estate_mobile/core/constants/app_colors.dart'; import 'package:real_estate_mobile/core/widgets/s3_image.dart'; import 'package:real_estate_mobile/features/agents/data/models/agent_profile.dart'; import 'package:real_estate_mobile/features/agents/presentation/providers/agent_detail_provider.dart'; import 'package:real_estate_mobile/features/auth/presentation/providers/auth_provider.dart'; import 'package:real_estate_mobile/features/home/presentation/widgets/home_header.dart'; class AgentDetailScreen extends ConsumerStatefulWidget { final String agentId; const AgentDetailScreen({super.key, required this.agentId}); @override ConsumerState createState() => _AgentDetailScreenState(); } class _AgentDetailScreenState extends ConsumerState { bool _emailVisible = false; bool _phoneVisible = false; @override void initState() { super.initState(); _loadConnectionIfAuth(); } void _loadConnectionIfAuth() { WidgetsBinding.instance.addPostFrameCallback((_) { final authState = ref.read(authProvider); if (authState.status == AuthStatus.authenticated) { ref.read(agentDetailProvider(widget.agentId).notifier).loadConnectionStatus(); } }); } @override Widget build(BuildContext context) { final state = ref.watch(agentDetailProvider(widget.agentId)); return Scaffold( backgroundColor: Colors.white, body: state.isLoading ? const Center( child: CircularProgressIndicator(color: AppColors.accentOrange)) : state.error != null ? _buildError(state.error!) : _buildContent(state), bottomNavigationBar: _buildBottomNavBar(), ); } Widget _buildError(String error) { return Center( child: Padding( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.error_outline, size: 48, color: AppColors.accentOrange), const SizedBox(height: 16), const Text('Unable to Load Profile', style: TextStyle( fontFamily: 'Fractul', fontSize: 18, fontWeight: FontWeight.w700, color: AppColors.primaryDark)), const SizedBox(height: 12), TextButton( onPressed: () => context.pop(), child: const Text('Go Back', style: TextStyle(color: AppColors.accentOrange)), ), ], ), ), ); } Widget _buildContent(AgentDetailState state) { final agent = state.agent!; return SingleChildScrollView( child: Column( children: [ SafeArea(bottom: false, child: const HomeHeader()), // ── Avatar Section ── const SizedBox(height: 16), _buildAvatarSection(agent), // ── Status + Connect Buttons ── const SizedBox(height: 16), _buildStatusButtons(state), // ── Contact Info ── const SizedBox(height: 12), _buildContactInfo(state), // ── Profile Info ── const SizedBox(height: 16), _buildProfileInfo(agent, state), // ── Experience Section ── if (_hasExperience(state)) ...[ const SizedBox(height: 24), _buildExperienceSection(state), ], // ── Info Cards (Availability, Work Env, Best Experience) ── const SizedBox(height: 24), _buildInfoCards(state), // ── Specialization Section ── if (state.specializationCards.isNotEmpty) ...[ const SizedBox(height: 24), _buildSpecializationSection(state), ], // ── Testimonials Section ── if (state.testimonials.isNotEmpty) ...[ const SizedBox(height: 24), _buildTestimonialsSection(state), ], const SizedBox(height: 30), ], ), ); } // ── Avatar ── Widget _buildAvatarSection(AgentProfile agent) { final screenWidth = MediaQuery.of(context).size.width; final avatarWidth = screenWidth * 0.877; // 377/430 from Figma return Center( child: ClipRRect( borderRadius: BorderRadius.circular(20), child: SizedBox( width: avatarWidth, height: 346, child: Stack( fit: StackFit.expand, children: [ S3Image( imageUrl: agent.avatarUrl, width: avatarWidth, height: 346, fit: BoxFit.cover, errorWidget: (_) => Container( color: const Color(0xFFC4D9D4), child: const Icon(Icons.person, size: 80, color: AppColors.primaryDark), ), ), // Gradient overlay Positioned( bottom: 0, left: 0, right: 0, height: 100, child: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.bottomCenter, end: Alignment.topCenter, colors: [ Colors.black.withValues(alpha: 0.5), Colors.transparent, ], ), ), ), ), ], ), ), ), ); } // ── Status + Connect ── Widget _buildStatusButtons(AgentDetailState state) { final agent = state.agent!; final isAvailable = agent.isAvailable; final connState = state.connectionState; return Padding( padding: const EdgeInsets.symmetric(horizontal: 38), child: Column( children: [ // Available row _buildStatusRow( isAvailable: true, label: 'Available.', active: isAvailable, connState: connState, onConnect: isAvailable ? () => _handleConnect(state) : null, ), const SizedBox(height: 10), // Unavailable row _buildStatusRow( isAvailable: false, label: 'Unavailable.', active: !isAvailable, connState: connState, onConnect: !isAvailable ? () => _handleConnect(state) : null, ), ], ), ); } Widget _buildStatusRow({ required bool isAvailable, required String label, required bool active, required String connState, VoidCallback? onConnect, }) { return Container( height: 55, decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), border: Border.all( color: AppColors.primaryDark.withValues(alpha: 0.15), width: 1), ), child: Row( children: [ const SizedBox(width: 20), // Status dot Container( width: 14, height: 12, decoration: BoxDecoration( shape: BoxShape.circle, color: active ? (isAvailable ? const Color(0xFF4CAF50) : Colors.transparent) : Colors.transparent, border: Border.all( color: active ? (isAvailable ? const Color(0xFF4CAF50) : AppColors.primaryDark.withValues(alpha: 0.3)) : AppColors.primaryDark.withValues(alpha: 0.15), width: 2, ), ), ), const SizedBox(width: 12), Text( label, style: TextStyle( fontFamily: 'Fractul', fontSize: 12, fontWeight: FontWeight.w700, color: active ? AppColors.primaryDark : AppColors.primaryDark.withValues(alpha: 0.4), ), ), const Spacer(), // Connect button if (active) _buildConnectButton(connState, isAvailable, onConnect), const SizedBox(width: 4), ], ), ); } Widget _buildConnectButton( String connState, bool isAvailable, VoidCallback? onConnect) { Color bgColor; Color textColor; String label; bool enabled; switch (connState) { case 'pending': bgColor = const Color(0xFFFFF3CD); textColor = const Color(0xFF856404); label = 'Pending'; enabled = false; break; case 'accepted': bgColor = AppColors.primaryDark; textColor = const Color(0xFFF0F5FC); label = 'Unlink'; enabled = true; break; default: bgColor = isAvailable ? AppColors.accentOrange : AppColors.primaryDark; textColor = isAvailable ? AppColors.primaryDark : const Color(0xFFF0F5FC); label = 'Connect'; enabled = true; } return GestureDetector( onTap: enabled ? onConnect : null, child: Container( height: 55, width: 164, decoration: BoxDecoration( color: bgColor, borderRadius: BorderRadius.circular(15), border: isAvailable && connState == 'none' ? Border.all(color: AppColors.accentOrange) : null, ), alignment: Alignment.center, child: Text( label, style: TextStyle( fontFamily: 'Fractul', fontSize: 14, fontWeight: FontWeight.w400, color: textColor, ), ), ), ); } void _handleConnect(AgentDetailState state) { final authState = ref.read(authProvider); if (authState.status != AuthStatus.authenticated) { context.push('/login'); return; } if (state.connectionState == 'accepted') { ref.read(agentDetailProvider(widget.agentId).notifier).cancelConnection(); return; } _showConnectModal(state); } void _showConnectModal(AgentDetailState state) { final controller = TextEditingController(); showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (ctx) => Padding( padding: EdgeInsets.only( bottom: MediaQuery.of(ctx).viewInsets.bottom), child: Container( decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Connect with ${state.agent?.fullName ?? ''}', style: const TextStyle( fontFamily: 'Fractul', fontSize: 18, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), ), GestureDetector( onTap: () => Navigator.pop(ctx), child: const Icon(Icons.close, color: AppColors.primaryDark), ), ], ), const SizedBox(height: 8), const Text( 'Send a connection request to this agent.', style: TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, color: AppColors.primaryDark, ), ), const SizedBox(height: 16), TextField( controller: controller, maxLines: 4, maxLength: 1000, decoration: InputDecoration( hintText: 'Introduce yourself... (optional)', hintStyle: TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, color: AppColors.primaryDark.withValues(alpha: 0.4), ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: BorderSide( color: AppColors.primaryDark.withValues(alpha: 0.15)), ), ), ), const SizedBox(height: 16), SizedBox( width: double.infinity, height: 50, child: ElevatedButton( onPressed: () async { Navigator.pop(ctx); await ref .read(agentDetailProvider(widget.agentId).notifier) .connect(message: controller.text); }, style: ElevatedButton.styleFrom( backgroundColor: AppColors.accentOrange, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15)), ), child: const Text( 'Send Request', style: TextStyle( fontFamily: 'Fractul', fontSize: 16, fontWeight: FontWeight.w700, color: Colors.white, ), ), ), ), ], ), ), ), ); } // ── Contact Info ── Widget _buildContactInfo(AgentDetailState state) { final email = state.contactEmail; final phone = state.contactPhone; if (email == null && phone == null) return const SizedBox.shrink(); return Padding( padding: const EdgeInsets.symmetric(horizontal: 38), child: Container( height: 98, padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 16), decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), border: Border.all( color: AppColors.primaryDark.withValues(alpha: 0.15), width: 1), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ if (email != null) _buildContactRow( label: 'Email:', value: _emailVisible ? email : state.maskEmail(email), onToggle: () => setState(() => _emailVisible = !_emailVisible), visible: _emailVisible, ), if (email != null && phone != null) const SizedBox(height: 12), if (phone != null) _buildContactRow( label: 'Ph.No:', value: _phoneVisible ? phone : state.maskPhone(phone), onToggle: () => setState(() => _phoneVisible = !_phoneVisible), visible: _phoneVisible, ), ], ), ), ); } Widget _buildContactRow({ required String label, required String value, required VoidCallback onToggle, required bool visible, }) { return Row( children: [ Text( label, style: const TextStyle( fontFamily: 'Fractul', fontSize: 14, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), ), const SizedBox(width: 8), Expanded( child: Text( value, style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, color: AppColors.primaryDark, ), ), ), const SizedBox(width: 8), GestureDetector( onTap: onToggle, child: Icon( visible ? Icons.visibility : Icons.visibility_off, size: 18, color: AppColors.primaryDark.withValues(alpha: 0.6), ), ), ], ); } // ── Profile Info (Name, Title, Location, Bio) ── Widget _buildProfileInfo(AgentProfile agent, AgentDetailState state) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 38), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ // Name + Verified badge + (Verified Expert) on same line Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.baseline, textBaseline: TextBaseline.alphabetic, children: [ Text( agent.firstName, style: const TextStyle( fontFamily: 'Fractul', fontSize: 18, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), ), const SizedBox(width: 4), if (agent.isVerified) ...[ const Padding( padding: EdgeInsets.only(bottom: 1), child: Icon(Icons.verified, size: 16, color: Color(0xFF2196F3)), ), const SizedBox(width: 4), ], Text( agent.lastName, style: const TextStyle( fontFamily: 'Fractul', fontSize: 18, fontWeight: FontWeight.w400, color: AppColors.primaryDark, ), ), if (agent.isVerified) ...[ const SizedBox(width: 6), const Text( '(Verified Expert)', style: TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, fontWeight: FontWeight.w500, color: Color(0xFF638559), ), ), ], ], ), // Title if (agent.agentType != null) ...[ const SizedBox(height: 6), Text( agent.headline ?? agent.agentType!.name, style: const TextStyle( fontFamily: 'Fractul', fontSize: 14, color: AppColors.primaryDark, ), textAlign: TextAlign.center, ), ], const SizedBox(height: 8), // Location + Member Since Row( mainAxisAlignment: MainAxisAlignment.center, children: [ if (agent.location.isNotEmpty) ...[ Icon(Icons.location_on, size: 16, color: AppColors.primaryDark.withValues(alpha: 0.7)), const SizedBox(width: 4), Text( agent.location, style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), ), const SizedBox(width: 20), ], Icon(Icons.calendar_today, size: 14, color: AppColors.primaryDark.withValues(alpha: 0.7)), const SizedBox(width: 4), Text( agent.memberSinceText, style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 13, fontWeight: FontWeight.w500, color: AppColors.primaryDark, ), ), ], ), // Bio if (agent.description.isNotEmpty) ...[ const SizedBox(height: 16), SizedBox( width: 338, child: Text( agent.description, style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, color: AppColors.primaryDark, height: 1.43, ), textAlign: TextAlign.center, ), ), ], ], ), ); } // ── Experience ── bool _hasExperience(AgentDetailState s) => s.yearsInExperience != '-' || s.contractsClosed != '-' || s.licensingAreas.isNotEmpty || s.expertiseYears.isNotEmpty || s.certifications.isNotEmpty; Widget _buildExperienceSection(AgentDetailState state) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 17), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Center( child: Text( 'Experience', style: TextStyle( fontFamily: 'Fractul', fontSize: 20, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), ), ), const SizedBox(height: 22), // Years if (state.yearsInExperience != '-') _buildExperienceItem( 'Years in Experience', [state.yearsInExperience]), // Contracts if (state.contractsClosed != '-') _buildExperienceItem( 'Number of contracts closed', [state.contractsClosed]), // Licensing Areas if (state.licensingAreas.isNotEmpty) _buildLicensingAreas(state.licensingAreas), // Expertise Years if (state.expertiseYears.isNotEmpty) _buildExpertiseYears(state.expertiseYears), // Certifications if (state.certifications.isNotEmpty) _buildCertifications(state.certifications), ], ), ); } Widget _buildExperienceItem(String title, List items) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: const TextStyle( fontFamily: 'Fractul', fontSize: 14, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), ), const SizedBox(height: 14), ...items.map((item) => Padding( padding: const EdgeInsets.only(left: 10, bottom: 4), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Padding( padding: EdgeInsets.only(top: 2), child: Text('\u2022 ', style: TextStyle(fontSize: 15, color: AppColors.primaryDark)), ), Expanded( child: Text( item, style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 15, fontWeight: FontWeight.w500, color: AppColors.primaryDark, ), ), ), ], ), )), const SizedBox(height: 16), Divider(color: AppColors.primaryDark.withValues(alpha: 0.15), height: 1), const SizedBox(height: 10), ], ); } Widget _buildLicensingAreas(List areas) { return _ExpandableChipsSection( title: 'Licensing & Areas', items: areas, initialCount: 6, ); } Widget _buildExpertiseYears(List> items) { final chips = items.map((e) { final years = e['years'] ?? ''; return years.isNotEmpty ? '${e['area']} – $years' : e['area'] ?? ''; }).toList(); return _ExpandableChipsSection( title: 'Areas in expertise & Years', items: chips, initialCount: 4, ); } Widget _buildCertifications(List> certs) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Certifications', style: TextStyle( fontFamily: 'Fractul', fontSize: 14, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), ), const SizedBox(height: 16), ...certs.map((cert) => Padding( padding: const EdgeInsets.only(left: 14, bottom: 16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Padding( padding: EdgeInsets.only(top: 2), child: Text('\u2022 ', style: TextStyle( fontSize: 14, color: AppColors.primaryDark)), ), Expanded( child: Text( cert['name'] ?? '', style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, color: AppColors.primaryDark, ), ), ), ], ), if ((cert['org'] ?? '').isNotEmpty) Padding( padding: const EdgeInsets.only(left: 20, top: 2), child: Text( cert['org']!.toUpperCase(), style: TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, color: AppColors.primaryDark.withValues(alpha: 0.5), ), ), ), ], ), )), const SizedBox(height: 4), ], ); } // ── Info Cards (Availability, Work Env, Best Experience) ── Widget _buildInfoCards(AgentDetailState state) { // Work environment and best experience from fieldValues String workEnv = ''; String bestExp = ''; for (final fv in state.fieldValues) { final slug = fv.fieldSlug.toLowerCase(); if (slug.contains('work_environment') || slug.contains('preferred_environment')) { if (fv.textValue != null) workEnv = fv.textValue!; if (fv.jsonValue is List) { workEnv = (fv.jsonValue as List).map((e) => e.toString()).join(', '); } } if (slug.contains('best_experience') || slug.contains('amazing_experience')) { if (fv.textValue != null) bestExp = fv.textValue!; } } return Padding( padding: const EdgeInsets.symmetric(horizontal: 36), child: Column( children: [ // Availability card if (state.availabilityType.isNotEmpty) _buildInfoCard( icon: Icons.access_time_filled, title: 'Availability', subtitle: state.availabilityType, items: state.availabilitySchedule, ), if (workEnv.isNotEmpty) ...[ const SizedBox(height: 16), _buildInfoCard( icon: Icons.landscape_outlined, title: '', subtitle: '', description: workEnv, ), ], if (bestExp.isNotEmpty) ...[ const SizedBox(height: 16), _buildInfoCard( icon: Icons.star_rounded, title: '', subtitle: '', description: bestExp, ), ], ], ), ); } Widget _buildInfoCard({ required IconData icon, required String title, required String subtitle, List? items, String? description, }) { return Container( width: double.infinity, constraints: const BoxConstraints(minHeight: 217), padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(10), boxShadow: [ BoxShadow( color: const Color(0xFFD9D9D9).withValues(alpha: 0.5), offset: const Offset(0, 10), blurRadius: 20, ), ], ), child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ Icon(icon, size: 36, color: AppColors.accentOrange), if (title.isNotEmpty) ...[ const SizedBox(height: 8), Text( title, style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.primaryDark, ), ), ], if (subtitle.isNotEmpty) ...[ const SizedBox(height: 2), Text( subtitle, style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.primaryDark, ), ), ], if (items != null && items.isNotEmpty) ...[ const SizedBox(height: 8), ...items.map((item) => Padding( padding: const EdgeInsets.symmetric(vertical: 1), child: Text( item, style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, color: AppColors.primaryDark, ), ), )), ], if (description != null) ...[ const SizedBox(height: 8), Text( description, style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.primaryDark, ), textAlign: TextAlign.center, ), ], ], ), ); } // ── Specialization Section ── Widget _buildSpecializationSection(AgentDetailState state) { return Container( width: double.infinity, color: const Color(0xFFE6E6E6), padding: const EdgeInsets.symmetric(vertical: 24), child: Column( children: [ const Text( 'Specialization', style: TextStyle( fontFamily: 'Fractul', fontSize: 20, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), ), const SizedBox(height: 4), const Text( 'Area Of Expertise and Focus', style: TextStyle( fontFamily: 'Fractul', fontSize: 14, fontWeight: FontWeight.w500, color: AppColors.primaryDark, ), ), const SizedBox(height: 16), ...state.specializationCards .map((card) => Padding( padding: const EdgeInsets.only(bottom: 12), child: _SpecializationCardWidget(card: card), )), ], ), ); } // ── Testimonials ── Widget _buildTestimonialsSection(AgentDetailState state) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: Column( children: [ const Text( 'Testimonials', style: TextStyle( fontFamily: 'Fractul', fontSize: 20, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), ), const SizedBox(height: 8), Divider( color: AppColors.primaryDark.withValues(alpha: 0.15), height: 1), const SizedBox(height: 8), const Text( 'Clients rate our real estate services 4.9 out of 5 on average, based on recent client reviews.', style: TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, color: AppColors.primaryDark, ), textAlign: TextAlign.center, ), const SizedBox(height: 16), SizedBox( height: 320, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: state.testimonials.length, separatorBuilder: (_, __) => const SizedBox(width: 12), itemBuilder: (_, i) => _TestimonialCard(data: state.testimonials[i]), ), ), ], ), ); } // ── Bottom Nav ── Widget _buildBottomNavBar() { return Container( decoration: const BoxDecoration( color: Colors.white, border: Border(top: BorderSide(color: Color(0xFFE8E8E8), width: 1)), ), child: SafeArea( top: false, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ _buildNavItem( svgPath: 'assets/icons/nav_home_icon.svg', fallbackIcon: Icons.home, onTap: () => context.go('/home'), ), _buildNavItem( svgPath: 'assets/icons/nav_list_icon.svg', fallbackIcon: Icons.list, isSelected: true, onTap: () => context.go('/agents/search'), ), _buildNavItem( svgPath: 'assets/icons/nav_messages_icon.svg', fallbackIcon: Icons.chat_bubble, onTap: () { if (ref.read(authProvider).status != AuthStatus.authenticated) { context.push('/login'); return; } context.go('/messages'); }, ), _buildNavItem( svgPath: 'assets/icons/nav_profile_icon.svg', fallbackIcon: Icons.person_outline, onTap: () { if (ref.read(authProvider).status != AuthStatus.authenticated) { context.push('/login'); return; } context.push('/profile'); }, ), ], ), ), ), ); } Widget _buildNavItem({ required String svgPath, required IconData fallbackIcon, bool isSelected = false, required VoidCallback onTap, }) { return GestureDetector( onTap: onTap, behavior: HitTestBehavior.opaque, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: SvgPicture.asset( svgPath, width: 24, height: 24, colorFilter: ColorFilter.mode( isSelected ? AppColors.primaryDark : AppColors.accentOrange, BlendMode.srcIn, ), placeholderBuilder: (_) => Icon( fallbackIcon, size: 24, color: isSelected ? AppColors.primaryDark : AppColors.accentOrange, ), ), ), ); } } // ── Expandable Chips Section ── class _ExpandableChipsSection extends StatefulWidget { final String title; final List items; final int initialCount; const _ExpandableChipsSection({ required this.title, required this.items, this.initialCount = 6, }); @override State<_ExpandableChipsSection> createState() => _ExpandableChipsSectionState(); } class _ExpandableChipsSectionState extends State<_ExpandableChipsSection> { bool _expanded = false; @override Widget build(BuildContext context) { final showItems = _expanded ? widget.items : widget.items.take(widget.initialCount).toList(); final hasMore = widget.items.length > widget.initialCount; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.title, style: const TextStyle( fontFamily: 'Fractul', fontSize: 14, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), ), const SizedBox(height: 14), Wrap( spacing: 10, runSpacing: 10, children: [ ...showItems.map((item) => IntrinsicWidth( child: Container( height: 28, padding: const EdgeInsets.symmetric(horizontal: 10), alignment: Alignment.center, decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), border: Border.all( color: AppColors.primaryDark, width: 0.7), ), child: Text( item, style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, fontWeight: FontWeight.w500, color: AppColors.primaryDark, ), ), ), )), if (hasMore && !_expanded) GestureDetector( onTap: () => setState(() => _expanded = true), child: IntrinsicWidth( child: Container( height: 28, padding: const EdgeInsets.symmetric(horizontal: 10), alignment: Alignment.center, decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), border: Border.all( color: AppColors.primaryDark.withValues(alpha: 0.15), width: 1), ), child: Text( '+${widget.items.length - widget.initialCount} More', style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 15, fontWeight: FontWeight.w300, color: AppColors.primaryDark, ), ), ), ), ), ], ), const SizedBox(height: 14), Divider(color: AppColors.primaryDark.withValues(alpha: 0.15), height: 1), const SizedBox(height: 10), ], ); } } // ── Specialization Card ── class _SpecializationCardWidget extends StatefulWidget { final SpecializationCard card; const _SpecializationCardWidget({required this.card}); @override State<_SpecializationCardWidget> createState() => _SpecializationCardWidgetState(); } class _SpecializationCardWidgetState extends State<_SpecializationCardWidget> { bool _expanded = false; static const _initialCount = 3; IconData get _icon { final slug = widget.card.slug.toLowerCase(); if (slug.contains('loan') || slug.contains('price')) { return Icons.attach_money; } if (slug.contains('property')) return Icons.apartment; if (slug.contains('hobby') || slug.contains('interest')) { return Icons.favorite_border; } return Icons.home_outlined; } @override Widget build(BuildContext context) { final values = _expanded ? widget.card.values : widget.card.values.take(_initialCount).toList(); final hasMore = widget.card.values.length > _initialCount; return Container( width: 298, constraints: const BoxConstraints(minHeight: 236), padding: const EdgeInsets.symmetric(vertical: 16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(15), border: Border.all( color: AppColors.primaryDark.withValues(alpha: 0.7), width: 0.7), ), child: Column( children: [ Icon(_icon, size: 32, color: AppColors.accentOrange), const SizedBox(height: 8), Text( widget.card.name, style: const TextStyle( fontFamily: 'Fractul', fontSize: 14, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), ), const SizedBox(height: 12), ...values.map((v) => Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Text( AgentDetailState.titleCase(v), style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, color: AppColors.primaryDark, height: 1.6, ), textAlign: TextAlign.center, ), )), if (hasMore) ...[ const SizedBox(height: 10), GestureDetector( onTap: () => setState(() => _expanded = !_expanded), child: Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 4), decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), border: Border.all(color: AppColors.accentOrange, width: 1), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Text( _expanded ? 'Show Less' : 'Show More', style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 10, fontWeight: FontWeight.w700, color: AppColors.accentOrange, ), ), const SizedBox(width: 2), Icon( _expanded ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down, size: 14, color: AppColors.accentOrange, ), ], ), ), ), ], ], ), ); } } // ── Testimonial Card ── class _TestimonialCard extends StatelessWidget { final Map data; const _TestimonialCard({required this.data}); @override Widget build(BuildContext context) { final text = data['text'] as String? ?? ''; final authorName = data['authorName'] as String? ?? ''; final authorRole = data['authorRole'] as String? ?? ''; final rating = (data['rating'] as num?)?.toInt() ?? 5; return SizedBox( width: 320, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Main card body with speech bubble shape Expanded( child: Container( width: double.infinity, padding: const EdgeInsets.fromLTRB(20, 16, 20, 20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(15), boxShadow: [ BoxShadow( color: const Color(0xFFD9D9D9).withValues(alpha: 0.5), offset: const Offset(0, 10), blurRadius: 20, ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Quote icon const Text( '\u201C', style: TextStyle( fontFamily: 'Fractul', fontSize: 36, fontWeight: FontWeight.w700, color: AppColors.primaryDark, height: 0.8, ), ), const SizedBox(height: 6), // Title snippet Text( text.length > 35 ? '${text.substring(0, 35)} ..' : text, style: const TextStyle( fontFamily: 'Fractul', fontSize: 14, fontWeight: FontWeight.w700, color: AppColors.primaryDark, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 10), // Full text Expanded( child: Text( text, style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, color: AppColors.primaryDark, height: 1.5, ), overflow: TextOverflow.fade, ), ), ], ), ), ), // Speech bubble triangle Padding( padding: const EdgeInsets.only(left: 30), child: CustomPaint( size: const Size(16, 10), painter: _TrianglePainter(color: Colors.white), ), ), const SizedBox(height: 6), // Stars + Author info Padding( padding: const EdgeInsets.only(left: 4), child: Row( children: [ // Stars Row( mainAxisSize: MainAxisSize.min, children: List.generate( 5, (i) => Icon( i < rating ? Icons.star : Icons.star_border, size: 16, color: i < rating ? AppColors.accentOrange : AppColors.primaryDark.withValues(alpha: 0.3), ), ), ), const SizedBox(width: 10), // Author Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( authorName, style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, fontWeight: FontWeight.w500, color: AppColors.primaryDark, ), ), if (authorRole.isNotEmpty) Text( authorRole, style: const TextStyle( fontFamily: 'SourceSerif4', fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.primaryDark, ), ), ], ), ), ], ), ), ], ), ); } } // ── Triangle painter for speech bubble ── class _TrianglePainter extends CustomPainter { final Color color; _TrianglePainter({required this.color}); @override void paint(Canvas canvas, Size size) { final paint = Paint() ..color = color ..style = PaintingStyle.fill; final shadowPaint = Paint() ..color = const Color(0xFFD9D9D9).withAlpha(40) ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 4); final path = Path() ..moveTo(0, 0) ..lineTo(size.width, 0) ..lineTo(size.width / 2, size.height) ..close(); canvas.drawPath(path, shadowPaint); canvas.drawPath(path, paint); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; }