import 'package:cached_network_image/cached_network_image.dart'; import 'package:onufitness/services/logger_service.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart'; import 'package:onufitness/constants/color_constant.dart'; import 'package:onufitness/constants/text_constant.dart'; import 'package:onufitness/screens/echoboard/controllers/echoboard_controller.dart'; import 'package:onufitness/screens/echoboard/models/post_model.dart'; import 'package:onufitness/screens/echoboard/views/single_post_screen.dart'; import 'package:onufitness/screens/home/controllers/home_controller.dart'; import 'package:onufitness/screens/home/widgets/empty_data_widget.dart'; import 'package:onufitness/screens/navbar/bottom_nav_bar.dart'; import 'package:onufitness/utils/custom_sneakbar.dart'; class TopPostSection extends StatefulWidget { final FitnessController controller; final EchoBoardController echoBoardController; final NavigationController navController; const TopPostSection({ super.key, required this.controller, required this.echoBoardController, required this.navController, }); @override State createState() => _TopPostSectionState(); } class _TopPostSectionState extends State { final logger = LoggerService(); void finalLoadPost(String postId) { try { widget.echoBoardController.loadSpecificPost(postId: postId); return; } catch (e) { logger.error("Catch : Error getting EchoBoardController on attempt : $e"); } } @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(16.w), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20.r), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.05), blurRadius: 15, offset: Offset(0, 5), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildHeader(context), SizedBox(height: 20.h), Obx(() { if (widget.controller.isTopPostLoading.value) { return _buildLoadingState(); } if (widget.controller.topPostData.value.data == null || widget.controller.topPostData.value.data!.items == null || widget.controller.topPostData.value.data!.items!.isEmpty) { return emptyChartWidget(); } return _buildPostsList(); }), ], ), ); } Widget _buildHeader(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Top Performing Posts', style: TextStyle( fontSize: mediumSizeText, fontWeight: FontWeight.w600, color: Colors.black87, ), ), SizedBox(height: 16.h), Row( children: [ Expanded(child: _buildDateButton(context, true)), SizedBox(width: 12.w), Icon(Icons.arrow_forward, size: 20.w, color: Colors.grey), SizedBox(width: 12.w), Expanded(child: _buildDateButton(context, false)), ], ), SizedBox(height: 16.h), ], ); } Widget _buildDateButton(BuildContext context, bool isStartDate) { return Obx(() { DateTime date = isStartDate ? widget.controller.topPostStartDate.value : widget.controller.topPostEndDate.value; return InkWell( onTap: () => _selectDate(context, isStartDate), child: Container( padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 10.h), decoration: BoxDecoration( border: Border.all(color: Colors.grey[300]!), borderRadius: BorderRadius.circular(12.r), ), child: Row( children: [ Icon(Icons.calendar_today, size: 15.w, color: Colors.grey[600]), SizedBox(width: 8.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( isStartDate ? 'Start Date' : 'End Date', style: TextStyle( fontSize: verySmallSizeText, color: Colors.grey[600], ), ), Text( DateFormat('MMM dd, yyyy').format(date), style: TextStyle( fontSize: verySmallSizeText, fontWeight: FontWeight.w600, color: Colors.black, ), ), ], ), ), ], ), ), ); }); } Future _selectDate(BuildContext context, bool isStartDate) async { DateTime initialDate = isStartDate ? widget.controller.topPostStartDate.value : widget.controller.topPostEndDate.value; // Step 1: Pick the Date final DateTime? pickedDate = await showDatePicker( context: context, initialDate: initialDate, firstDate: DateTime(2020), lastDate: DateTime.now(), builder: (context, child) { return Theme( data: Theme.of(context).copyWith( colorScheme: ColorScheme.light( primary: Color(primaryColor), onPrimary: Colors.white, ), ), child: child!, ); }, ); if (pickedDate != null) { // Step 2: Add current time automatically final now = DateTime.now(); final DateTime fullPickedDate = DateTime( pickedDate.year, pickedDate.month, pickedDate.day, now.hour, now.minute, now.second, ); if (isStartDate) { if (fullPickedDate.isBefore(widget.controller.topPostEndDate.value) || fullPickedDate.isAtSameMomentAs( widget.controller.topPostEndDate.value, )) { widget.controller.updateTopPostDateRange( fullPickedDate, widget.controller.topPostEndDate.value, ); } else { customSnackbar( title: 'Invalid Date Range', message: 'Start date must be before end date', duration: 2, ); } } else { if (fullPickedDate.isAfter(widget.controller.topPostStartDate.value) || fullPickedDate.isAtSameMomentAs( widget.controller.topPostStartDate.value, )) { widget.controller.updateTopPostDateRange( widget.controller.topPostStartDate.value, fullPickedDate, ); } else { customSnackbar( title: 'Invalid Date Range', message: 'End date must be after start date', duration: 2, ); } } } } Widget _buildLoadingState() { return SizedBox( height: 200.h, child: Center( child: CircularProgressIndicator(color: Color(primaryColor)), ), ); } Widget _buildPostsList() { final posts = widget.controller.topPostData.value.data!.items!; return Column( children: List.generate( posts.length, (index) => InkWell( onTap: () { Get.to( () => SinglePostPage( postId: posts[index].postId.toString(), tribeId: 0, ), ); }, child: topFivePostCard(posts[index], index), ), ), ); } Widget topFivePostCard(dynamic post, int index) { final hasMedia = post.mediaFiles != null && post.mediaFiles!.isNotEmpty; final hasPoll = post.poll != null; final hasContent = post.content != null && post.content!.isNotEmpty; return Container( margin: EdgeInsets.only(bottom: 16.h), padding: EdgeInsets.all(16.w), decoration: BoxDecoration( color: Colors.grey[50], borderRadius: BorderRadius.circular(16.r), border: Border.all(color: Colors.grey[200]!), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildPostHeader(post, index), if (hasContent) ...[SizedBox(height: 12.h), _buildPostContent(post)], if (hasMedia) ...[ SizedBox(height: 12.h), _buildMediaPreview(post.mediaFiles!), ], if (hasPoll) ...[ SizedBox(height: 12.h), _buildPollWidget(post.poll!), ], SizedBox(height: 12.h), _buildPostStats(post), ], ), ); } Widget _buildPostHeader(dynamic post, int index) { return Row( children: [ Container( padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 4.h), decoration: BoxDecoration( color: Color(primaryColor), borderRadius: BorderRadius.circular(12.r), ), child: Text( '#${index + 1}', style: TextStyle( fontSize: verySmallSizeText, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), SizedBox(width: 12.w), CircleAvatar( radius: 20.r, backgroundColor: Colors.grey[300], backgroundImage: post.profilePicture != null && post.profilePicture!.isNotEmpty ? CachedNetworkImageProvider(post.profilePicture!) : null, child: post.profilePicture == null || post.profilePicture!.isEmpty ? Icon(Icons.person, size: 20.w, color: Colors.grey[600]) : null, ), SizedBox(width: 12.w), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( post.fullName ?? 'Unknown User', style: TextStyle( fontSize: smallSizeText, fontWeight: FontWeight.w600, color: Colors.black87, ), ), if (post.userTypeName != null) ...[ SizedBox(height: 2.h), Text( post.userTypeName!, style: TextStyle( fontSize: verySmallSizeText, color: Colors.grey[600], ), ), ], ], ), ), if (post.createdAt != null) Text( _getTimeAgo(post.createdAt!), style: TextStyle( fontSize: verySmallSizeText, color: Colors.grey[600], ), ), ], ); } Widget _buildPostContent(dynamic post) { return Text( post.content!, style: TextStyle( fontSize: smallSizeText, color: Colors.black87, height: 1.4, ), maxLines: 4, overflow: TextOverflow.ellipsis, ); } Widget _buildMediaPreview(List mediaFiles) { if (mediaFiles.isEmpty) return const SizedBox.shrink(); final displayCount = mediaFiles.length > 4 ? 4 : mediaFiles.length; return SizedBox( height: 200.h, child: GridView.builder( physics: const NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: displayCount == 1 ? 1 : 2, crossAxisSpacing: 8.w, mainAxisSpacing: 8.h, childAspectRatio: displayCount == 1 ? 16 / 9 : 1, ), itemCount: displayCount, itemBuilder: (context, index) { final media = mediaFiles[index]; final isVideo = media.mediaType?.toLowerCase() == 'video'; final imageUrl = media.mediaFile ?? ''; final videoThumbnail = media.thumbnailFile ?? ''; // If it's a video, prefer thumbnail final displayUrl = isVideo ? (videoThumbnail.isNotEmpty ? videoThumbnail : "") : imageUrl; return Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(12.r), color: Colors.grey[300], ), child: ClipRRect( borderRadius: BorderRadius.circular(12.r), child: Stack( fit: StackFit.expand, children: [ if (displayUrl.isNotEmpty) CachedNetworkImage( imageUrl: displayUrl, fit: BoxFit.cover, placeholder: (context, url) => Center( child: CircularProgressIndicator( color: Color(primaryColor), strokeWidth: 2, ), ), errorWidget: (context, url, error) => Icon( isVideo ? Icons.videocam : Icons.image, color: Colors.grey[600], size: 40.w, ), ) else Icon( isVideo ? Icons.videocam : Icons.image, color: Colors.grey[600], size: 40.w, ), if (isVideo) Container( color: Colors.black26, child: Center( child: Container( padding: EdgeInsets.all(8.w), decoration: const BoxDecoration( color: Colors.black54, shape: BoxShape.circle, ), child: Icon( Icons.play_arrow, color: Colors.white, size: 32.w, ), ), ), ), if (index == 3 && mediaFiles.length > 4) Container( color: Colors.black54, child: Center( child: Text( '+${mediaFiles.length - 4}', style: TextStyle( color: Colors.white, fontSize: largeSizeText, fontWeight: FontWeight.bold, ), ), ), ), ], ), ), ); }, ), ); } Widget _buildPollWidget(dynamic poll) { final hasOptions = poll.options != null && poll.options!.isNotEmpty; return Container( padding: EdgeInsets.all(14.w), decoration: BoxDecoration( gradient: LinearGradient( colors: [Color(primaryColor).withOpacity(0.05), Colors.white], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(12.r), border: Border.all(color: Color(primaryColor).withOpacity(0.2)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( padding: EdgeInsets.all(6.w), decoration: BoxDecoration( color: Color(primaryColor).withOpacity(0.1), borderRadius: BorderRadius.circular(8.r), ), child: Icon( Icons.poll_rounded, size: 18.w, color: Color(primaryColor), ), ), SizedBox(width: 10.w), Expanded( child: Text( poll.question ?? 'Poll Question', style: TextStyle( fontSize: smallSizeText, fontWeight: FontWeight.w600, color: Colors.black87, ), ), ), ], ), if (hasOptions) ...[ SizedBox(height: 14.h), ...List.generate(poll.options!.length, (index) { final option = poll.options![index]; final percentage = option.percentageOptionPollVotes ?? 0; final hasVoted = option.hasUserVoted ?? false; return Padding( padding: EdgeInsets.only(bottom: 10.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( option.optionLabel ?? 'Option ${index + 1}', style: TextStyle( fontSize: smallSizeText, color: Colors.black87, fontWeight: hasVoted ? FontWeight.w600 : FontWeight.w500, ), ), ), SizedBox(width: 8.w), Text( '$percentage%', style: TextStyle( fontSize: smallSizeText, fontWeight: FontWeight.w700, color: Color(primaryColor), ), ), ], ), SizedBox(height: 6.h), Stack( children: [ Container( height: 8.h, decoration: BoxDecoration( color: Colors.grey[200], borderRadius: BorderRadius.circular(4.r), ), ), FractionallySizedBox( widthFactor: percentage / 100, child: Container( height: 8.h, decoration: BoxDecoration( gradient: LinearGradient( colors: [ Color(primaryColor), Color(primaryColor).withOpacity(0.7), ], ), borderRadius: BorderRadius.circular(4.r), ), ), ), ], ), if (hasVoted) ...[ SizedBox(height: 4.h), Row( children: [ Icon( Icons.check_circle, size: 12.w, color: Color(primaryColor), ), SizedBox(width: 4.w), Text( 'Your vote', style: TextStyle( fontSize: verySmallSizeText, color: Color(primaryColor), fontWeight: FontWeight.w600, ), ), ], ), ], ], ), ); }), ], SizedBox(height: 8.h), Row( children: [ Icon(Icons.how_to_vote, size: 14.w, color: Colors.grey[600]), SizedBox(width: 6.w), Text( '${poll.totalPollVotes ?? 0} total votes', style: TextStyle( fontSize: verySmallSizeText, color: Colors.grey[700], fontWeight: FontWeight.w500, ), ), ], ), ], ), ); } Widget _buildPostStats(dynamic post) { return Row( children: [ _buildStatItem( Icons.favorite, post.totalPostReactions?.toString() ?? '0', 'Reactions', ), SizedBox(width: 20.w), _buildStatItem( Icons.chat_bubble, post.totalPostComments?.toString() ?? '0', 'Comments', ), ], ); } Widget _buildStatItem(IconData icon, String count, String label) { return Row( children: [ Icon(icon, size: 16.w, color: Colors.grey[600]), SizedBox(width: 6.w), Text( count, style: TextStyle( fontSize: verySmallSizeText, fontWeight: FontWeight.w600, color: Colors.black87, ), ), SizedBox(width: 4.w), Text( label, style: TextStyle( fontSize: verySmallSizeText, color: Colors.grey[600], ), ), ], ); } String _getTimeAgo(DateTime dateTime) { final now = DateTime.now(); final difference = now.difference(dateTime); if (difference.inDays > 365) { return '${(difference.inDays / 365).floor()}y ago'; } else if (difference.inDays > 30) { return '${(difference.inDays / 30).floor()}mo ago'; } else if (difference.inDays > 0) { return '${difference.inDays}d ago'; } else if (difference.inHours > 0) { return '${difference.inHours}h ago'; } else if (difference.inMinutes > 0) { return '${difference.inMinutes}m ago'; } else { return 'Just now'; } } }