diff --git a/lib/features/about/presentation/screens/about_screen.dart b/lib/features/about/presentation/screens/about_screen.dart index c9b7853..871b92a 100644 --- a/lib/features/about/presentation/screens/about_screen.dart +++ b/lib/features/about/presentation/screens/about_screen.dart @@ -73,22 +73,22 @@ class _AboutFeatureItem { } } -class _AboutTeamMember { - final String name; - final String role; - final String imageUrl; +class _AboutTeamCard { + final String iconPath; + final String title; + final String description; - const _AboutTeamMember({ - required this.name, - required this.role, - required this.imageUrl, + const _AboutTeamCard({ + required this.iconPath, + required this.title, + required this.description, }); - factory _AboutTeamMember.fromJson(Map json) { - return _AboutTeamMember( - name: json['name'] as String? ?? '', - role: json['role'] as String? ?? '', - imageUrl: json['imageUrl'] as String? ?? '', + factory _AboutTeamCard.fromJson(Map json) { + return _AboutTeamCard( + iconPath: json['iconPath'] as String? ?? '', + title: json['title'] as String? ?? '', + description: json['description'] as String? ?? '', ); } } @@ -143,11 +143,7 @@ const _defaultFeatures = [ ), ]; -const _defaultTeamMembers = [ - _AboutTeamMember(name: 'Andrew', role: 'Founder & CEO', imageUrl: 'asset://professional-1'), - _AboutTeamMember(name: 'Thomas', role: 'Co-Founder', imageUrl: 'asset://professional-2'), - _AboutTeamMember(name: 'Darren', role: 'Advisor', imageUrl: 'asset://professional-3'), -]; +const _defaultTeamCards = <_AboutTeamCard>[]; // ── State ── @@ -159,7 +155,8 @@ class _AboutState { final List<_AboutFeatureItem> features; final String teamTitle; final String teamSubtitle; - final List<_AboutTeamMember> teamMembers; + final String teamIntro; + final List<_AboutTeamCard> teamCards; final _AboutCta cta; final int activeTeamIndex; @@ -172,7 +169,8 @@ class _AboutState { this.teamTitle = 'Meet the minds behind the platform', this.teamSubtitle = 'We are a team of passionate innovators, real estate experts, and designers working together to redefine how people connect with trusted agents.', - this.teamMembers = _defaultTeamMembers, + this.teamIntro = '', + this.teamCards = _defaultTeamCards, this.cta = const _AboutCta(), this.activeTeamIndex = 0, }); @@ -185,7 +183,8 @@ class _AboutState { List<_AboutFeatureItem>? features, String? teamTitle, String? teamSubtitle, - List<_AboutTeamMember>? teamMembers, + String? teamIntro, + List<_AboutTeamCard>? teamCards, _AboutCta? cta, int? activeTeamIndex, }) { @@ -197,7 +196,8 @@ class _AboutState { features: features ?? this.features, teamTitle: teamTitle ?? this.teamTitle, teamSubtitle: teamSubtitle ?? this.teamSubtitle, - teamMembers: teamMembers ?? this.teamMembers, + teamIntro: teamIntro ?? this.teamIntro, + teamCards: teamCards ?? this.teamCards, cta: cta ?? this.cta, activeTeamIndex: activeTeamIndex ?? this.activeTeamIndex, ); @@ -252,13 +252,15 @@ class _AboutNotifier extends StateNotifier<_AboutState> { case 'team': final title = content['title'] as String?; final subtitle = content['subtitle'] as String?; - final membersList = content['members'] as List?; + final intro = content['intro'] as String?; + final cardsList = content['cards'] as List?; if (title != null) state = state.copyWith(teamTitle: title); if (subtitle != null) state = state.copyWith(teamSubtitle: subtitle); - if (membersList != null && membersList.isNotEmpty) { + if (intro != null) state = state.copyWith(teamIntro: intro); + if (cardsList != null && cardsList.isNotEmpty) { state = state.copyWith( - teamMembers: membersList - .map((e) => _AboutTeamMember.fromJson(e as Map)) + teamCards: cardsList + .map((e) => _AboutTeamCard.fromJson(e as Map)) .toList(), ); } @@ -594,192 +596,144 @@ class AboutScreen extends ConsumerWidget { ); } - // -- Team Section (dynamic with carousel) -- + // -- Team Section (dynamic — matches web: title, subtitle, intro, icon cards) -- Widget _buildTeamSection( BuildContext context, WidgetRef ref, _AboutState state, ) { - final members = state.teamMembers; - if (members.isEmpty) return const SizedBox.shrink(); - - final activeIndex = state.activeTeamIndex.clamp(0, members.length - 1); - final screenWidth = MediaQuery.of(context).size.width; - final cardWidth = screenWidth - (69 * 2); + final cards = state.teamCards; + final hasContent = state.teamTitle.isNotEmpty || + state.teamSubtitle.isNotEmpty || + state.teamIntro.isNotEmpty || + cards.isNotEmpty; + if (!hasContent) return const SizedBox.shrink(); return Column( children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 57), - child: Text( - state.teamTitle, - textAlign: TextAlign.center, - style: const TextStyle( - fontFamily: 'Fractul', - fontSize: 25, - fontWeight: FontWeight.w700, - color: AppColors.primaryDark, - ), - ), - ), - const SizedBox(height: 16), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 57), - child: Text( - state.teamSubtitle, - textAlign: TextAlign.center, - style: const TextStyle( - fontFamily: 'SourceSerif4', - fontSize: 14, - fontWeight: FontWeight.w400, - color: AppColors.primaryDark, - ), - ), - ), - const SizedBox(height: 20), - // Team carousel - SizedBox( - height: 330, - child: PageView.builder( - itemCount: members.length, - controller: PageController( - viewportFraction: cardWidth / screenWidth, - ), - onPageChanged: (index) { - ref.read(_aboutProvider.notifier).setActiveTeamIndex(index); - }, - itemBuilder: (context, index) { - final member = members[index]; - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(15), - border: Border.all( - color: AppColors.primaryDark.withValues(alpha: 0.1), - width: 0.1, - ), - ), - child: Column( - children: [ - const SizedBox(height: 19), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: ClipRRect( - borderRadius: BorderRadius.circular(15), - child: SizedBox( - height: 211, - width: double.infinity, - child: _buildTeamMemberImage(member.imageUrl, index), - ), - ), - ), - const SizedBox(height: 16), - Text( - member.name, - textAlign: TextAlign.center, - style: const TextStyle( - fontFamily: 'Fractul', - fontSize: 14, - fontWeight: FontWeight.w700, - color: Colors.black, - ), - ), - const SizedBox(height: 4), - Text( - member.role, - textAlign: TextAlign.center, - style: const TextStyle( - fontFamily: 'Fractul', - fontSize: 14, - fontWeight: FontWeight.w300, - color: Colors.black, - ), - ), - ], - ), - ), - ); - }, - ), - ), - const SizedBox(height: 16), - // Page indicator - if (members.length > 1) + if (state.teamTitle.isNotEmpty) Padding( - padding: const EdgeInsets.symmetric(horizontal: 58), - child: LayoutBuilder( - builder: (context, constraints) { - final totalWidth = constraints.maxWidth; - final segmentWidth = totalWidth / members.length; - return Stack( - children: [ - Container( - height: 5, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(15), - border: Border.all( - color: AppColors.primaryDark.withValues(alpha: 0.1), - width: 0.1, - ), - ), - ), - AnimatedPositioned( - duration: const Duration(milliseconds: 200), - left: segmentWidth * activeIndex, - child: Container( - height: 5, - width: segmentWidth, - decoration: BoxDecoration( - color: AppColors.primaryDark, - borderRadius: BorderRadius.circular(15), - ), - ), - ), - ], - ); - }, + padding: const EdgeInsets.symmetric(horizontal: 57), + child: Text( + state.teamTitle, + textAlign: TextAlign.center, + style: const TextStyle( + fontFamily: 'Fractul', + fontSize: 25, + fontWeight: FontWeight.w700, + color: AppColors.primaryDark, + ), ), ), + if (state.teamSubtitle.isNotEmpty) ...[ + const SizedBox(height: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 57), + child: Text( + state.teamSubtitle, + textAlign: TextAlign.center, + style: const TextStyle( + fontFamily: 'SourceSerif4', + fontSize: 14, + fontWeight: FontWeight.w400, + color: AppColors.primaryDark, + ), + ), + ), + ], + if (state.teamIntro.isNotEmpty) ...[ + const SizedBox(height: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Text( + state.teamIntro, + textAlign: TextAlign.center, + style: const TextStyle( + fontFamily: 'SourceSerif4', + fontSize: 14, + fontWeight: FontWeight.w400, + color: AppColors.primaryDark, + height: 1.5, + ), + ), + ), + ], + if (cards.isNotEmpty) ...[ + const SizedBox(height: 24), + ...cards.map( + (card) => Padding( + padding: const EdgeInsets.fromLTRB(24, 0, 24, 15), + child: _buildTeamCard(card), + ), + ), + ], ], ); } - // -- Team member image (handles asset://, S3 keys, and URLs) -- - // Falls back to local professional-N.jpg assets when CMS image fails. - Widget _buildTeamMemberImage(String imageUrl, int index) { - // Local fallback asset based on index - final fallbackAsset = 'assets/images/professional-${index + 1}.jpg'; - Widget fallbackImage() => Image.asset( - fallbackAsset, - height: 211, - width: double.infinity, - fit: BoxFit.cover, - errorBuilder: (_, __, ___) => const _TeamMemberPlaceholder(), - ); - - if (imageUrl.isEmpty) return fallbackImage(); - - // Local asset images (defaults) - if (imageUrl.startsWith('asset://')) { - final name = imageUrl.replaceFirst('asset://', ''); - return Image.asset( - 'assets/images/$name.jpg', - height: 211, - width: double.infinity, - fit: BoxFit.cover, - errorBuilder: (_, __, ___) => fallbackImage(), - ); - } - - // S3 key or URL from CMS — fall back to local asset on error - return S3Image( - imageUrl: imageUrl, - height: 211, - fit: BoxFit.cover, - placeholder: (_) => fallbackImage(), - errorWidget: (_) => fallbackImage(), + Widget _buildTeamCard(_AboutTeamCard card) { + return Container( + width: double.infinity, + padding: const EdgeInsets.all(24), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15), + border: Border.all( + color: AppColors.primaryDark.withValues(alpha: 0.1), + width: 1, + ), + ), + child: Column( + children: [ + if (card.iconPath.isNotEmpty) + SizedBox( + width: 36, + height: 36, + child: card.iconPath.startsWith('http') + ? Image.network( + card.iconPath, + width: 36, + height: 36, + fit: BoxFit.contain, + errorBuilder: (_, __, ___) => const SizedBox.shrink(), + ) + : S3Image( + imageUrl: card.iconPath, + width: 36, + height: 36, + fit: BoxFit.contain, + placeholder: (_) => const SizedBox.shrink(), + errorWidget: (_) => const SizedBox.shrink(), + ), + ), + if (card.title.isNotEmpty) ...[ + const SizedBox(height: 12), + Text( + card.title, + textAlign: TextAlign.center, + style: const TextStyle( + fontFamily: 'Fractul', + fontSize: 16, + fontWeight: FontWeight.w700, + color: AppColors.primaryDark, + ), + ), + ], + if (card.description.isNotEmpty) ...[ + const SizedBox(height: 8), + Text( + card.description, + textAlign: TextAlign.center, + style: const TextStyle( + fontFamily: 'SourceSerif4', + fontSize: 14, + fontWeight: FontWeight.w400, + color: AppColors.primaryDark, + height: 1.5, + ), + ), + ], + ], + ), ); } @@ -892,16 +846,3 @@ class AboutScreen extends ConsumerWidget { } -class _TeamMemberPlaceholder extends StatelessWidget { - const _TeamMemberPlaceholder(); - - @override - Widget build(BuildContext context) { - return Container( - color: const Color(0xFFC4D9D4), - child: const Center( - child: Icon(Icons.person, size: 60, color: AppColors.primaryDark), - ), - ); - } -}