import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../theme/app_theme.dart'; import '../../widgets/app_scaffold.dart'; import '../../widgets/media/signed_media_image.dart'; import 'viewable_profile_screen.dart'; import 'package:timeago/timeago.dart' as timeago; /// Following screen with community management features /// Shows followed users with tabs and sorting options class FollowingScreen extends ConsumerStatefulWidget { const FollowingScreen({super.key}); @override ConsumerState createState() => _FollowingScreenState(); } // Tab filter options enum FollowingTab { all('All'), vouched('Vouched'), newFollows('New'); final String displayName; const FollowingTab(this.displayName); } // Sort options for following list enum FollowingSort { highestHarmony('Highest Harmony'), lastActive('Last Active'), recentlyFollowed('Recently Followed'); final String displayName; const FollowingSort(this.displayName); } class _FollowingScreenState extends ConsumerState { FollowingTab _activeTab = FollowingTab.all; FollowingSort _activeSort = FollowingSort.highestHarmony; List _followedUsers = []; bool _isLoading = false; String? _error; @override void initState() { super.initState(); _loadFollowing(); } void _setStateIfMounted(VoidCallback fn) { if (!mounted) return; setState(fn); } Future _loadFollowing() async { _setStateIfMounted(() { _isLoading = true; _error = null; }); try { // For now, we'll use a mock implementation since we need an API endpoint await Future.delayed(const Duration(milliseconds: 500)); _setStateIfMounted(() { _followedUsers = _generateMockData(); }); } catch (e) { _setStateIfMounted(() { _error = e.toString(); }); } finally { _setStateIfMounted(() { _isLoading = false; }); } } List _generateMockData() { return [ FollowedUser( id: '1', handle: 'alice', displayName: 'Alice Johnson', avatarUrl: null, harmonyScore: 92, tier: 'trusted', lastActive: DateTime.now().subtract(const Duration(hours: 1)), latestPostBody: 'Just published my latest thoughts on...', latestPostCreatedAt: DateTime.now().subtract(const Duration(hours: 2)), followedAt: DateTime.now().subtract(const Duration(days: 30)), ), FollowedUser( id: '2', handle: 'bob', displayName: 'Bob Smith', avatarUrl: null, harmonyScore: 78, tier: 'established', lastActive: DateTime.now().subtract(const Duration(hours: 3)), latestPostBody: 'Working on something exciting!', latestPostCreatedAt: DateTime.now().subtract(const Duration(hours: 4)), followedAt: DateTime.now().subtract(const Duration(days: 15)), ), FollowedUser( id: '3', handle: 'carol', displayName: 'Carol Williams', avatarUrl: null, harmonyScore: 85, tier: 'trusted', lastActive: DateTime.now().subtract(const Duration(minutes: 30)), latestPostBody: 'Check out this amazing discovery...', latestPostCreatedAt: DateTime.now().subtract(const Duration(hours: 1)), followedAt: DateTime.now().subtract(const Duration(days: 5)), ), FollowedUser( id: '4', handle: 'dave', displayName: 'Dave Brown', avatarUrl: null, harmonyScore: 45, tier: 'new', lastActive: DateTime.now().subtract(const Duration(days: 1)), latestPostBody: 'Hello sojorn! This is my first post.', latestPostCreatedAt: DateTime.now().subtract(const Duration(days: 2)), followedAt: DateTime.now().subtract(const Duration(days: 1)), ), FollowedUser( id: '5', handle: 'eve', displayName: 'Eve Davis', avatarUrl: null, harmonyScore: 88, tier: 'trusted', lastActive: DateTime.now().subtract(const Duration(minutes: 15)), latestPostBody: 'The sunset today was absolutely beautiful.', latestPostCreatedAt: DateTime.now().subtract(const Duration(minutes: 20)), followedAt: DateTime.now().subtract(const Duration(days: 60)), ), ]; } List _getFilteredAndSortedUsers() { List filtered = _followedUsers; switch (_activeTab) { case FollowingTab.all: break; case FollowingTab.vouched: filtered = filtered.where((u) => u.tier == 'trusted').toList(); break; case FollowingTab.newFollows: final weekAgo = DateTime.now().subtract(const Duration(days: 7)); filtered = filtered.where((u) => u.followedAt.isAfter(weekAgo)).toList(); break; } switch (_activeSort) { case FollowingSort.highestHarmony: filtered.sort((a, b) => b.harmonyScore.compareTo(a.harmonyScore)); break; case FollowingSort.lastActive: filtered.sort((a, b) => b.lastActive.compareTo(a.lastActive)); break; case FollowingSort.recentlyFollowed: filtered.sort((a, b) => b.followedAt.compareTo(a.followedAt)); break; } return filtered; } Color _getTierColor(String tier) { switch (tier) { case 'trusted': return AppTheme.tierTrusted; case 'established': return AppTheme.tierEstablished; default: return AppTheme.tierNew; } } @override Widget build(BuildContext context) { final filteredUsers = _getFilteredAndSortedUsers(); return AppScaffold( title: 'Following', body: Column( children: [ _buildTabBar(), _buildSortDropdown(), Expanded( child: _error != null ? _buildErrorState() : _isLoading && _followedUsers.isEmpty ? const Center(child: CircularProgressIndicator()) : filteredUsers.isEmpty ? _buildEmptyState() : _buildUserList(filteredUsers), ), ], ), ); } Widget _buildTabBar() { return Container( decoration: BoxDecoration( color: AppTheme.cardSurface, border: Border( bottom: BorderSide(color: AppTheme.egyptianBlue.withOpacity(0.3)), ), ), child: TabBar( tabs: FollowingTab.values.map((tab) { return Tab(text: tab.displayName); }).toList(), labelColor: AppTheme.navyBlue, unselectedLabelColor: AppTheme.egyptianBlue.withOpacity(0.5), indicatorColor: AppTheme.royalPurple, indicatorWeight: 3, onTap: (index) { setState(() { _activeTab = FollowingTab.values[index]; }); }, ), ); } Widget _buildSortDropdown() { return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: AppTheme.scaffoldBg, border: Border( bottom: BorderSide(color: AppTheme.egyptianBlue.withOpacity(0.2)), ), ), child: Row( children: [ Text( 'Sort by:', style: AppTheme.labelSmall.copyWith( color: AppTheme.egyptianBlue, ), ), const SizedBox(width: 8), DropdownButton( value: _activeSort, icon: Icon( Icons.arrow_drop_down, color: AppTheme.royalPurple, ), elevation: 2, style: AppTheme.labelSmall.copyWith( color: AppTheme.navyBlue, ), underline: Container( height: 1, color: AppTheme.royalPurple.withOpacity(0.3), ), onChanged: (FollowingSort? newValue) { if (newValue != null) { setState(() { _activeSort = newValue; }); } }, items: FollowingSort.values.map((sort) { return DropdownMenuItem( value: sort, child: Text(sort.displayName), ); }).toList(), ), ], ), ); } Widget _buildUserList(List users) { return ListView.builder( padding: const EdgeInsets.symmetric(vertical: 8), itemCount: users.length, itemBuilder: (context, index) { final user = users[index]; return _UserListItem( user: user, tierColor: _getTierColor(user.tier), onTap: () { Navigator.of(context).push( MaterialPageRoute( builder: (_) => UnifiedProfileScreen(handle: user.handle), ), ); }, onPostTap: () {}, ); }, ); } Widget _buildErrorState() { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( _error ?? 'Something went wrong', style: AppTheme.bodyMedium.copyWith( color: AppTheme.error, ), textAlign: TextAlign.center, ), const SizedBox(height: AppTheme.spacingMd), ElevatedButton( onPressed: _loadFollowing, child: const Text('Retry'), ), ], ), ); } Widget _buildEmptyState() { String message; switch (_activeTab) { case FollowingTab.all: message = "You're not following anyone yet."; break; case FollowingTab.vouched: message = 'No vouched users in your following list.'; break; case FollowingTab.newFollows: message = 'No new follows in the last 7 days.'; break; } return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.people_outline, size: 64, color: AppTheme.egyptianBlue.withOpacity(0.5), ), const SizedBox(height: AppTheme.spacingMd), Text( message, style: AppTheme.bodyMedium.copyWith( color: AppTheme.egyptianBlue, ), textAlign: TextAlign.center, ), ], ), ); } } /// Model representing a followed user with their latest post preview class FollowedUser { final String id; final String handle; final String displayName; final String? avatarUrl; final int harmonyScore; final String tier; final DateTime lastActive; final String? latestPostBody; final DateTime? latestPostCreatedAt; final DateTime followedAt; const FollowedUser({ required this.id, required this.handle, required this.displayName, this.avatarUrl, required this.harmonyScore, required this.tier, required this.lastActive, this.latestPostBody, this.latestPostCreatedAt, required this.followedAt, }); } /// Individual user list item in the following screen class _UserListItem extends StatelessWidget { final FollowedUser user; final Color tierColor; final VoidCallback onTap; final VoidCallback onPostTap; const _UserListItem({ required this.user, required this.tierColor, required this.onTap, required this.onPostTap, }); @override Widget build(BuildContext context) { return InkWell( onTap: onTap, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ CircleAvatar( radius: 24, backgroundColor: tierColor, child: user.avatarUrl != null ? ClipRRect( borderRadius: BorderRadius.circular(24), child: SignedMediaImage( url: user.avatarUrl!, width: 48, height: 48, fit: BoxFit.cover, ), ) : Text( user.displayName.isNotEmpty ? user.displayName[0].toUpperCase() : '?', style: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 18, ), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( user.displayName, style: TextStyle( color: AppTheme.navyBlue, fontWeight: FontWeight.bold, fontSize: 16, ), ), const SizedBox(width: 8), Container( width: 8, height: 8, decoration: BoxDecoration( color: tierColor, shape: BoxShape.circle, ), ), ], ), Text( '@${user.handle}', style: AppTheme.labelSmall.copyWith( color: AppTheme.egyptianBlue, ), ), const SizedBox(height: 6), if (user.latestPostBody != null && user.latestPostBody!.isNotEmpty) GestureDetector( onTap: onPostTap, child: Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: AppTheme.scaffoldBg, borderRadius: BorderRadius.circular(8), border: Border.all( color: AppTheme.egyptianBlue.withOpacity(0.2), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _truncateText(user.latestPostBody!, 100), style: AppTheme.bodyMedium.copyWith( color: AppTheme.navyText, ), maxLines: 2, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), Text( user.latestPostCreatedAt != null ? timeago.format(user.latestPostCreatedAt!) : '', style: AppTheme.labelSmall.copyWith( color: AppTheme.egyptianBlue.withOpacity(0.7), ), ), ], ), ), ), const SizedBox(height: 8), Row( children: [ Icon( Icons.access_time, size: 12, color: AppTheme.egyptianBlue.withOpacity(0.6), ), const SizedBox(width: 4), Text( 'Active ${timeago.format(user.lastActive)}', style: AppTheme.labelSmall.copyWith( color: AppTheme.egyptianBlue.withOpacity(0.7), ), ), const SizedBox(width: 12), Icon( Icons.shield, size: 12, color: tierColor, ), const SizedBox(width: 4), Text( '${user.harmonyScore}', style: AppTheme.labelSmall.copyWith( color: tierColor, fontWeight: FontWeight.w600, ), ), ], ), ], ), ), Icon( Icons.chevron_right, color: AppTheme.egyptianBlue.withOpacity(0.5), ), ], ), ), ); } String _truncateText(String text, int maxLength) { if (text.length <= maxLength) return text; return '${text.substring(0, maxLength)}...'; } }