onufitness_mobile/lib/screens/echoboard/views/echoboard_view_screen.dart
2026-01-13 11:36:24 +05:30

487 lines
18 KiB
Dart

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<EchoBoardViewScreen> createState() => _EchoBoardViewScreenState();
}
class _EchoBoardViewScreenState extends State<EchoBoardViewScreen>
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<NotificationController>();
final Set<String> _processingLikes = <String>{};
final Set<String> _processingComments = <String>{};
// ADD: Helper getter to check if this is a tribe echoboard
bool get isTribeEchoboard => widget.tribeId != 0;
@override
void initState() {
if (!Get.isRegistered<UvaultController>()) {
Get.put(UvaultController());
}
if (!Get.isRegistered<EchoBoardController>()) {
Get.put(EchoBoardController());
}
echoBoardController = Get.find<EchoBoardController>();
if (!Get.isRegistered<SocketService>()) {
Get.put(SocketService());
}
Get.find<SocketService>().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<SocialConnectionController>()) {
Get.put(SocialConnectionController());
}
socialConnectionController = Get.find<SocialConnectionController>();
if (!Get.isRegistered<LikeCommentController>()) {
Get.put(LikeCommentController());
}
likeCommentController = Get.find<LikeCommentController>();
if (!Get.isRegistered<ProfileController>()) {
Get.put(ProfileController());
}
profileController = Get.find<ProfileController>();
if (!Get.isRegistered<PollController>()) {
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<void> 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<void> 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<void> 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<void> _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),
),
),
],
),
);
}),
);
}
}