487 lines
18 KiB
Dart
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),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}),
|
|
);
|
|
}
|
|
}
|