import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:supabase_flutter/supabase_flutter.dart' as supabase; import '../../../core/widgets/app_scaffold.dart'; import '../../../core/widgets/loading_indicator.dart'; import '../../../core/utils/date_time_utils.dart'; import '../../../core/utils/unit_conversion_utils.dart'; import '../../../data/models/user_model.dart' as app; import '../../../data/models/goal_model.dart'; import '../../auth/application/auth_controller.dart'; import '../application/social_controller.dart'; import '../../profile/application/profile_controller.dart'; class PublicProfileScreen extends ConsumerStatefulWidget { final String userId; const PublicProfileScreen({ super.key, required this.userId, }); @override ConsumerState createState() => _PublicProfileScreenState(); } class _PublicProfileScreenState extends ConsumerState { Map? _userStats; List? _userGoals; bool _isLoadingStats = false; bool _isLoadingGoals = false; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { _loadProfile(); _checkFollowingStatus(); _loadUserStats(); _loadUserGoals(); }); } Future _loadProfile() async { await ref.read(profileControllerProvider.notifier).loadProfile(widget.userId); } Future _checkFollowingStatus() async { await ref.read(socialControllerProvider.notifier).isFollowing(widget.userId); } Future _loadUserStats() async { setState(() => _isLoadingStats = true); try { final client = supabase.Supabase.instance.client; final response = await client.rpc('get_user_stats', params: {'user_uuid': widget.userId}); setState(() { _userStats = response as Map; _isLoadingStats = false; }); } catch (e) { setState(() => _isLoadingStats = false); } } Future _loadUserGoals() async { setState(() => _isLoadingGoals = true); try { final client = supabase.Supabase.instance.client; final response = await client.rpc('get_public_user_goals', params: { 'user_uuid': widget.userId, 'limit_count': 10 }); setState(() { _userGoals = (response as List).map((json) => Goal.fromJson(json)).toList(); _isLoadingGoals = false; }); } catch (e) { setState(() => _isLoadingGoals = false); } } Future _toggleFollow() async { final controller = ref.read(socialControllerProvider.notifier); final isFollowing = await controller.isFollowing(widget.userId); if (isFollowing) { await controller.unfollowUser(widget.userId); } else { await controller.followUser(widget.userId); } setState(() {}); } @override Widget build(BuildContext context) { final profileState = ref.watch(profileControllerProvider); final authController = ref.watch(authControllerProvider); final isOwnProfile = authController?.id == widget.userId; return AppScaffold( title: 'Profile', body: _buildBody(profileState, isOwnProfile), ); } Widget _buildBody(ProfileState state, bool isOwnProfile) { if (state.isLoading) { return const Center(child: LoadingIndicator()); } if (state.errorMessage != null) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.error_outline, size: 48, color: Colors.red), const SizedBox(height: 16), Text('Error: ${state.errorMessage}'), ], ), ); } final user = state.user; if (user == null) { return const Center(child: Text('User not found')); } return RefreshIndicator( onRefresh: () async { await _loadProfile(); await _checkFollowingStatus(); await _loadUserStats(); await _loadUserGoals(); }, child: CustomScrollView( slivers: [ SliverToBoxAdapter( child: _ProfileHeader( user: user, isOwnProfile: isOwnProfile, onToggleFollow: isOwnProfile ? () {} : _toggleFollow, ), ), SliverToBoxAdapter( child: _EnhancedStatsSection( user: user, userStats: _userStats, isLoadingStats: _isLoadingStats, ), ), if (_userGoals != null && _userGoals!.isNotEmpty) SliverToBoxAdapter( child: _UserGoalsSection( goals: _userGoals!, isLoadingGoals: _isLoadingGoals, ), ), if (user.age != null || user.formattedHeight.isNotEmpty || user.formattedWeight.isNotEmpty) SliverToBoxAdapter( child: _BiometricSection(user: user), ), ], ), ); } } class _EnhancedStatsSection extends StatelessWidget { final app.User user; final Map? userStats; final bool isLoadingStats; const _EnhancedStatsSection({ required this.user, this.userStats, required this.isLoadingStats, }); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Profile Stats', style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, ), ), const SizedBox(height: 16), if (isLoadingStats) const Center(child: CircularProgressIndicator()) else Row( children: [ Expanded( child: _StatCard( icon: Icons.flag, title: 'Goals', value: '${userStats?['goals_count'] ?? 0}', ), ), const SizedBox(width: 12), Expanded( child: _StatCard( icon: Icons.check_circle, title: 'Completed', value: '${userStats?['completed_goals_count'] ?? 0}', ), ), ], ), const SizedBox(height: 12), Row( children: [ Expanded( child: _StatCard( icon: Icons.people, title: 'Followers', value: '${userStats?['followers_count'] ?? 0}', ), ), const SizedBox(width: 12), Expanded( child: _StatCard( icon: Icons.person_add, title: 'Following', value: '${userStats?['following_count'] ?? 0}', ), ), ], ), const SizedBox(height: 16), if (user.countdownStartDate != null) ...[ _StatCard( icon: Icons.timer, title: 'Challenge Started', value: DateTimeUtils.formatShortDate(user.countdownStartDate!), ), const SizedBox(height: 12), if (user.daysRemaining != null) _StatCard( icon: Icons.hourglass_empty, title: 'Days Remaining', value: '${user.daysRemaining} days', ), const SizedBox(height: 12), ], _StatCard( icon: Icons.calendar_today, title: 'Member Since', value: DateTimeUtils.formatShortDate(user.createdAt), ), ], ), ); } } class _UserGoalsSection extends StatelessWidget { final List goals; final bool isLoadingGoals; const _UserGoalsSection({ required this.goals, required this.isLoadingGoals, }); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Public Goals', style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, ), ), const SizedBox(height: 16), if (isLoadingGoals) const Center(child: CircularProgressIndicator()) else ...goals.map((goal) => Padding( padding: const EdgeInsets.only(bottom: 12), child: _GoalCard(goal: goal), )), ], ), ); } } class _BiometricSection extends StatelessWidget { final app.User user; const _BiometricSection({required this.user}); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Biometric Information', style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, ), ), const SizedBox(height: 16), Row( children: [ if (user.gender != null) Expanded( child: _BiometricCard( icon: user.gender!.emoji, title: 'Gender', value: user.gender!.displayName, ), ), if (user.age != null) Expanded( child: _BiometricCard( icon: '🎂', title: 'Age', value: '${user.age} years', ), ), ], ), if (user.gender != null && user.age != null) const SizedBox(height: 12), Row( children: [ if (user.formattedHeight.isNotEmpty) Expanded( child: _BiometricCard( icon: '📏', title: 'Height', value: user.formattedHeight, ), ), if (user.formattedWeight.isNotEmpty) Expanded( child: _BiometricCard( icon: '⚖️', title: 'Weight', value: user.formattedWeight, ), ), ], ), if (user.bmi != null) Column( children: [ const SizedBox(height: 12), _BiometricCard( icon: '💪', title: 'BMI', value: '${user.bmi!.toStringAsFixed(1)} - ${user.bmiCategory}', valueColor: UnitConversionUtils.getBmiColor(user.bmi!), ), ], ), ], ), ); } } class _BiometricCard extends StatelessWidget { final String icon; final String title; final String value; final Color? valueColor; const _BiometricCard({ required this.icon, required this.title, required this.value, this.valueColor, }); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Theme.of(context).colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(12), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), const SizedBox(height: 4), Row( children: [ Text( icon, style: const TextStyle(fontSize: 20), ), const SizedBox(width: 8), Expanded( child: Text( value, style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, color: valueColor ?? null, ), ), ), ], ), ], ), ); } } class _GoalCard extends StatelessWidget { final Goal goal; const _GoalCard({required this.goal}); @override Widget build(BuildContext context) { return Card( child: ListTile( leading: CircleAvatar( backgroundColor: goal.completed ? Theme.of(context).colorScheme.primaryContainer : Theme.of(context).colorScheme.surfaceContainerHighest, child: Icon( goal.completed ? Icons.check : Icons.flag_outlined, color: goal.completed ? Theme.of(context).colorScheme.onPrimaryContainer : Theme.of(context).colorScheme.onSurfaceVariant, ), ), title: Text( goal.title, style: TextStyle( decoration: goal.completed ? TextDecoration.lineThrough : null, ), ), subtitle: goal.description != null && goal.description!.isNotEmpty ? Text( goal.description!, maxLines: 2, overflow: TextOverflow.ellipsis, ) : null, trailing: goal.progress > 0 ? Text('${goal.progress}%') : null, ), ); } } class _ProfileHeader extends ConsumerWidget { final app.User user; final bool isOwnProfile; final VoidCallback onToggleFollow; const _ProfileHeader({ required this.user, required this.isOwnProfile, required this.onToggleFollow, }); @override Widget build(BuildContext context, WidgetRef ref) { return Container( padding: const EdgeInsets.all(24), child: Column( children: [ CircleAvatar( radius: 50, backgroundColor: Theme.of(context).colorScheme.primaryContainer, backgroundImage: user.avatarUrl != null ? CachedNetworkImageProvider(user.avatarUrl!) : null, child: user.avatarUrl == null ? Text( user.username.substring(0, 2).toUpperCase(), style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.onPrimaryContainer, ), ) : null, ), const SizedBox(height: 16), Text( user.username, style: Theme.of(context).textTheme.headlineSmall?.copyWith( fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), if (user.bio != null && user.bio!.isNotEmpty) Text( user.bio!, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), textAlign: TextAlign.center, ), const SizedBox(height: 8), if (user.isPublicProfile && ((user.twitterHandle != null && user.twitterHandle!.isNotEmpty) || (user.instagramHandle != null && user.instagramHandle!.isNotEmpty) || (user.tiktokHandle != null && user.tiktokHandle!.isNotEmpty) || (user.websiteUrl != null && user.websiteUrl!.isNotEmpty))) Wrap( spacing: 8, runSpacing: 4, alignment: WrapAlignment.center, children: [ if (user.twitterHandle != null && user.twitterHandle!.isNotEmpty) Chip( avatar: const Icon(Icons.alternate_email, size: 16), label: Text(user.twitterHandle!), ), if (user.instagramHandle != null && user.instagramHandle!.isNotEmpty) Chip( avatar: const Icon(Icons.camera_alt_outlined, size: 16), label: Text(user.instagramHandle!), ), if (user.tiktokHandle != null && user.tiktokHandle!.isNotEmpty) Chip( avatar: const Icon(Icons.music_note_outlined, size: 16), label: Text(user.tiktokHandle!), ), if (user.websiteUrl != null && user.websiteUrl!.isNotEmpty) Chip( avatar: const Icon(Icons.link, size: 16), label: Text(user.websiteUrl!), ), ], ), const SizedBox(height: 16), if (!isOwnProfile) _FollowButton(onToggleFollow: onToggleFollow), ], ), ); } } class _FollowButton extends StatelessWidget { final VoidCallback onToggleFollow; const _FollowButton({required this.onToggleFollow}); @override Widget build(BuildContext context) { return ElevatedButton.icon( onPressed: onToggleFollow, icon: const Icon(Icons.person_add), label: const Text('Follow'), style: ElevatedButton.styleFrom( minimumSize: const Size(120, 40), ), ); } } class _StatCard extends StatelessWidget { final IconData icon; final String title; final String value; const _StatCard({ required this.icon, required this.title, required this.value, }); @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Theme.of(context).colorScheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(12), ), child: Row( children: [ Icon( icon, color: Theme.of(context).colorScheme.primary, size: 32, ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: Theme.of(context).textTheme.bodySmall?.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), const SizedBox(height: 4), Text( value, style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, ), ), ], ), ), ], ), ); } }