import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:onufitness/services/logger_service.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; import 'package:onufitness/constants/asset_constants.dart'; import 'package:onufitness/constants/color_constant.dart'; import 'package:onufitness/constants/constant.dart'; import 'package:onufitness/constants/text_constant.dart'; import 'package:onufitness/environment/environment_config.dart'; import 'package:onufitness/controller/notification_controller.dart'; import 'package:onufitness/routes/route_constant.dart'; import 'package:onufitness/screens/echoboard/controllers/like_comment_controller.dart'; import 'package:onufitness/screens/echoboard/controllers/connection_and_tribe_controller.dart'; import 'package:onufitness/screens/echoboard/controllers/poll_controller.dart'; import 'package:onufitness/screens/echoboard/controllers/profile_controller.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/echoboard/widget/Like/reaction_popup.dart'; import 'package:onufitness/screens/echoboard/widget/comment_bottomsheet.dart'; import 'package:onufitness/screens/echoboard/widget/post_card.dart'; import 'package:onufitness/screens/echoboard/widget/post_three_dot_bottom_sheet.dart'; import 'package:onufitness/screens/streamming/screens/get_all_live_stream_screen.dart'; import 'package:onufitness/screens/u_vault/controllers/uvault_video_controller.dart'; import 'package:onufitness/services/local_storage_services/shared_services.dart'; import 'package:onufitness/services/socket/socket_service.dart'; import 'package:onufitness/utils/helper_function.dart'; import 'package:share_plus/share_plus.dart'; class EchoBoardViewScreen extends StatefulWidget { final int tribeId; const EchoBoardViewScreen({super.key, this.tribeId = 0}); @override State createState() => _EchoBoardViewScreenState(); } class _EchoBoardViewScreenState extends State with AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive => true; final logger = LoggerService(); late SocialConnectionController socialConnectionController; late LikeCommentController likeCommentController; late ProfileController profileController; final ScrollController _scrollController = ScrollController(); EchoBoardController? echoBoardController; final notificationController = Get.find(); final Set _processingLikes = {}; final Set _processingComments = {}; // ADD: Helper getter to check if this is a tribe echoboard bool get isTribeEchoboard => widget.tribeId != 0; @override void initState() { if (!Get.isRegistered()) { Get.put(UvaultController()); } if (!Get.isRegistered()) { Get.put(EchoBoardController()); } echoBoardController = Get.find(); if (!Get.isRegistered()) { Get.put(SocketService()); } Get.find().connect(); if (!isTribeEchoboard) { notificationController.fetchNotifications(isRefresh: true); } super.initState(); // Trigger fetch if no posts exist if (echoBoardController!.posts.isEmpty) { echoBoardController!.isPostFetchedLoading.value = true; if (isTribeEchoboard) { echoBoardController!.fetchPosts(refresh: true, tribeId: widget.tribeId); } else { echoBoardController!.fetchPosts(refresh: true); } } // Trigger fetch if there are only 1 post (Handelled this way For Post share ) if (echoBoardController!.posts.length == 1) { echoBoardController!.isPostFetchedLoading.value = true; if (isTribeEchoboard) { echoBoardController!.fetchPosts(refresh: true, tribeId: widget.tribeId); } else { echoBoardController!.fetchPosts(refresh: true); } } if (!Get.isRegistered()) { Get.put(SocialConnectionController()); } socialConnectionController = Get.find(); if (!Get.isRegistered()) { Get.put(LikeCommentController()); } likeCommentController = Get.find(); if (!Get.isRegistered()) { Get.put(ProfileController()); } profileController = Get.find(); if (!Get.isRegistered()) { Get.put(PollController()); } _scrollController.addListener(_onScroll); } void _onScroll() { if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200) { if (echoBoardController!.hasMoreData.value) { // Pass tribe context during pagination if (isTribeEchoboard) { echoBoardController!.fetchPosts(tribeId: widget.tribeId); } else { echoBoardController!.fetchPosts(); } } } } @override void dispose() { _scrollController.dispose(); super.dispose(); } Future handleLikePressed(SocialPostItem post) async { final postIdStr = post.postId.toString(); if (_processingLikes.contains(postIdStr)) return; _processingLikes.add(postIdStr); final wasLiked = post.postReactionTypeId != null; likeCommentController.updatePostReaction(postIdStr, 1, wasLiked); likeCommentController .submitPostReaction( postId: postIdStr, reactionTypeId: 1, isDeleteReaction: wasLiked, ) .then((success) { if (!success) { likeCommentController.updatePostReaction(postIdStr, 1, !wasLiked); } }) .whenComplete(() { _processingLikes.remove(postIdStr); }); } Future handleLongPress( SocialPostItem post, GlobalKey likeButtonKey, ) async { final postIdStr = post.postId.toString(); if (_processingLikes.contains(postIdStr)) return; showReactions( context: context, buttonKey: likeButtonKey, onReactionSelected: (reaction, reactionTypeID) async { if (_processingLikes.contains(postIdStr)) return; _processingLikes.add(postIdStr); likeCommentController.updatePostReaction( postIdStr, reactionTypeID, false, ); likeCommentController .submitPostReaction( postId: postIdStr, reactionTypeId: reactionTypeID, isDeleteReaction: false, ) .then((success) { if (!success) { likeCommentController.updatePostReaction( postIdStr, post.postReactionTypeId ?? 1, false, ); } }) .whenComplete(() { _processingLikes.remove(postIdStr); }); }, ); } Future handleCommentPressed(SocialPostItem post) async { final postIdStr = post.postId.toString(); if (_processingComments.contains(postIdStr)) return; _processingComments.add(postIdStr); try { likeCommentController.postId.value = postIdStr; if (mounted) { showCommentsBottomSheet(context, postIdStr, likeCommentController); } } finally { _processingComments.remove(postIdStr); } } // Centralized refresh handler that maintains tribe context Future _handleRefresh() async { if (isTribeEchoboard) { await echoBoardController!.fetchPosts( refresh: true, tribeId: widget.tribeId, ); } else { await echoBoardController!.refreshPosts(); } } @override Widget build(BuildContext context) { super.build(context); return Scaffold( backgroundColor: pageBackGroundColor, appBar: widget.tribeId != 0 ? null : AppBar( automaticallyImplyLeading: false, backgroundColor: Colors.white, title: Text( 'EchoBoard', style: TextStyle( fontSize: regularSizeText, fontWeight: FontWeight.w600, ), ), actions: [ IconButton( padding: EdgeInsets.zero, icon: Icon(Icons.search, color: Colors.black, size: 24.sp), onPressed: () async { socialConnectionController.currentTab.value = "Pending"; socialConnectionController.clearSearch(); Get.toNamed(RouteConstant.userSearchScreen); await socialConnectionController.searchUsers(); }, ), IconButton( icon: Icon(Icons.add, color: Colors.black, size: 24.sp), onPressed: () { Get.toNamed(RouteConstant.uploadSocialPostScreen); }, ), IconButton( icon: Image.asset( AssetConstants.chatIcon, height: 19.h, width: 19.w, color: Colors.black, ), onPressed: () { Get.toNamed(RouteConstant.chatListScreen); }, ), IconButton( icon: Image.asset( AssetConstants.connectionLogo, height: 19.h, width: 19.w, color: Colors.black, ), onPressed: () async { socialConnectionController.currentTab.value = "Invitations"; socialConnectionController.clearSearch(); Get.toNamed(RouteConstant.friendRequestScreen); await socialConnectionController.searchUsers(); }, ), IconButton( onPressed: () { Get.to(() => GetAllLiveStreamsScreen()); }, icon: Icon(Icons.stream, color: Colors.black, size: 23.sp), ), Stack( children: [ IconButton( icon: Icon( Icons.notifications_none, color: Colors.black, size: 24.sp, ), onPressed: () { notificationController.fetchNotifications( isRefresh: true, ); Get.toNamed(RouteConstant.notificationListScreen); }, ), notificationController.hasUnreadNotifications ? Positioned( right: isTablet ? 5.w : 12.w, top: isTablet ? 6.h : 12.h, child: Container( width: 8.r, height: 8.r, decoration: BoxDecoration( color: Colors.red, shape: BoxShape.circle, ), ), ) : SizedBox(), ], ), ], ), body: Obx(() { if (echoBoardController!.posts.isEmpty && echoBoardController!.isPostFetchedLoading.value) { return const Center( child: CircularProgressIndicator(color: Colors.black), ); } else if (echoBoardController!.posts.isEmpty) { // FIXED: Allow pull-to-refresh even with no posts return RefreshIndicator( color: Colors.black, backgroundColor: Colors.white, onRefresh: _handleRefresh, child: LayoutBuilder( builder: (context, constraints) { return SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: ConstrainedBox( constraints: BoxConstraints( minHeight: constraints.maxHeight, ), child: Center(child: Text("No post found")), ), ); }, ), ); } // FIXED: Use centralized refresh handler return RefreshIndicator( color: Colors.black, backgroundColor: Colors.white, onRefresh: _handleRefresh, child: CustomScrollView( controller: _scrollController, physics: const AlwaysScrollableScrollPhysics(), slivers: [ SliverList( delegate: SliverChildBuilderDelegate( (context, index) { if (index < echoBoardController!.posts.length) { final post = echoBoardController!.posts[index]; final GlobalKey likeButtonKey = GlobalKey(); return RepaintBoundary( child: PostCard( key: ValueKey( 'post_${post.postId}_${post.poll?.pollPostId}', ), post: post, isTribeEchoboard: isTribeEchoboard, onProfilePressed: () { profileController.selectedUserId.value = post.userId.toString(); Get.toNamed(RouteConstant.userSocialProfileScreen); }, onPostTap: () { Get.to( () => SinglePostPage( postId: post.postId.toString(), isComingFromShare: false, tribeId: widget.tribeId, ), ); }, onLikePressed: () => handleLikePressed(post), likeButtonKey: likeButtonKey, onLikeLongPressed: () => handleLongPress(post, likeButtonKey), onCommentPressed: () => handleCommentPressed(post), onSharePressed: () async { final encodedPostId = base64Url.encode( utf8.encode(post.postId.toString()), ); final encodedTribeId = base64Url.encode( utf8.encode(widget.tribeId.toString()), ); final webUrl = EnvironmentConfigFactory.getConfig().webUrl; final shareUrl = widget.tribeId != 0 ? "https://$webUrl/tribe-post-link?tribeId=$encodedTribeId&postId=$encodedPostId" : "https://$webUrl/post-link?postId=$encodedPostId"; final content = shareUrl; await SharePlus.instance.share( ShareParams( text: content, subject: 'Check out this post!', ), ); }, onMorePressed: () { final bool isMyPost = post.userId == SharedServices.getUserDetails()?.data?.userId; PostMoreOptionsBottomSheet.show( context: context, postId: post.postId.toString(), isMyPost: isMyPost, post: post, tribeId: widget.tribeId, ); }, onPollOptionSelected: (pollId, optionId) async { echoBoardController! .submitPollVote(pollId, optionId) .then((response) { if (response != null) { echoBoardController! .updatePostWithPollResults( response, selectedOptionId: optionId, ); } }); }, ), ); } else { return Center( child: Padding( padding: EdgeInsets.symmetric(vertical: 12.h), child: const CircularProgressIndicator( color: Colors.black, ), ), ); } }, childCount: echoBoardController!.posts.length + (echoBoardController!.isPaginationLoading.value && echoBoardController!.hasMoreData.value ? 1 : 0), ), ), ], ), ); }), ); } }