887 lines
32 KiB
Dart
887 lines
32 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
import 'package:onufitness/constants/api_enum_constant.dart';
|
|
import 'package:onufitness/constants/asset_constants.dart';
|
|
import 'package:onufitness/constants/color_constant.dart';
|
|
import 'package:onufitness/constants/text_constant.dart';
|
|
import 'package:onufitness/screens/echoboard/widget/image_zoom.dart';
|
|
import 'package:onufitness/screens/echoboard/models/post_model.dart';
|
|
import 'package:onufitness/screens/u_vault/widgets/video_card.dart';
|
|
import 'package:onufitness/utils/custom_cache_manager.dart';
|
|
import 'package:onufitness/utils/custom_sneakbar.dart';
|
|
import 'package:onufitness/utils/helper_function.dart';
|
|
import 'package:shimmer/shimmer.dart';
|
|
import 'package:cached_network_image/cached_network_image.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'dart:developer' as developer;
|
|
|
|
class PostCard extends StatefulWidget {
|
|
final SocialPostItem post;
|
|
final VoidCallback onLikePressed;
|
|
final VoidCallback onLikeLongPressed;
|
|
final VoidCallback onCommentPressed;
|
|
final VoidCallback onSharePressed;
|
|
final VoidCallback? onMorePressed;
|
|
final VoidCallback? onProfilePressed;
|
|
final VoidCallback? onPostTap;
|
|
final Function(int pollId, int optionId)? onPollOptionSelected;
|
|
final GlobalKey? likeButtonKey;
|
|
final bool isTribeEchoboard;
|
|
final bool isThreeDotButtonEnabled;
|
|
|
|
const PostCard({
|
|
super.key,
|
|
required this.post,
|
|
required this.onLikePressed,
|
|
required this.onCommentPressed,
|
|
required this.onSharePressed,
|
|
this.onMorePressed,
|
|
this.onPollOptionSelected,
|
|
required this.onLikeLongPressed,
|
|
this.likeButtonKey,
|
|
this.onProfilePressed,
|
|
this.isTribeEchoboard = false,
|
|
this.onPostTap,
|
|
this.isThreeDotButtonEnabled = true,
|
|
});
|
|
|
|
@override
|
|
PostCardState createState() => PostCardState();
|
|
}
|
|
|
|
class PostCardState extends State<PostCard> with AutomaticKeepAliveClientMixin {
|
|
String? _selectedPollOptionId;
|
|
//Do not remove _isVoting....
|
|
bool _isVoting = false;
|
|
|
|
bool _isContentExpanded = false;
|
|
static const int _contentPreviewLength = 150;
|
|
|
|
@override
|
|
bool get wantKeepAlive => true;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
// DON'T initialize video here - only set up poll state
|
|
_initializePollState();
|
|
}
|
|
|
|
void _initializePollState() {
|
|
// Check if user has already voted on this poll
|
|
if (_hasUserVotedOnAnyOption()) {
|
|
// Set the selected option ID if user has already voted
|
|
_setSelectedOptionFromUserVote();
|
|
}
|
|
}
|
|
|
|
@override
|
|
void didUpdateWidget(PostCard oldWidget) {
|
|
super.didUpdateWidget(oldWidget);
|
|
|
|
// If the post data changed (especially poll data), we might need to reset UI states
|
|
if (oldWidget.post.postId != widget.post.postId ||
|
|
oldWidget.post.poll?.pollPostId != widget.post.poll?.pollPostId) {
|
|
// Reset voting state
|
|
_isVoting = false;
|
|
|
|
// Update selected option based on API data
|
|
if (_hasUserVotedOnAnyOption()) {
|
|
_setSelectedOptionFromUserVote();
|
|
} else {
|
|
_selectedPollOptionId = null;
|
|
}
|
|
}
|
|
|
|
// If reaction changed, we should update the UI as well
|
|
if (oldWidget.post.postReactionTypeId != widget.post.postReactionTypeId) {
|
|
developer.log(
|
|
"Reaction type changed: ${oldWidget.post.postReactionTypeId} -> ${widget.post.postReactionTypeId}",
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
super.dispose();
|
|
}
|
|
|
|
// Check if the user has voted on any option in this poll
|
|
bool _hasUserVotedOnAnyOption() {
|
|
if (widget.post.poll?.options == null) return false;
|
|
return widget.post.poll!.options!.any(
|
|
(option) => option.hasUserVoted == true,
|
|
);
|
|
}
|
|
|
|
// Set the selected option ID from the user's vote
|
|
void _setSelectedOptionFromUserVote() {
|
|
if (widget.post.poll?.options != null) {
|
|
for (var option in widget.post.poll!.options!) {
|
|
if (option.hasUserVoted == true && option.optionId != null) {
|
|
_selectedPollOptionId = option.optionId.toString();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool _hasVideo() {
|
|
return widget.post.mediaFiles?.any(
|
|
(media) => media.mediaType?.toLowerCase().contains('video') ?? false,
|
|
) ??
|
|
false;
|
|
}
|
|
|
|
bool _hasImage() {
|
|
return widget.post.mediaFiles?.any(
|
|
(media) => media.mediaType?.toLowerCase().contains('image') ?? false,
|
|
) ??
|
|
false;
|
|
}
|
|
|
|
bool _hasPoll() {
|
|
return widget.post.poll != null;
|
|
}
|
|
|
|
//...............................................................................
|
|
// bool _isPollExpired() {
|
|
// if (widget.post.poll?.expiryDate == null) return false;
|
|
// return DateTime.now().isAfter(widget.post.poll!.expiryDate!);
|
|
// }
|
|
//...............................................................................
|
|
String? _getFirstImageUrl() {
|
|
if (!_hasImage()) return null;
|
|
final imageMedia = widget.post.mediaFiles!.firstWhere(
|
|
(media) => media.mediaType?.toLowerCase().contains('image') ?? false,
|
|
orElse: () => MediaFile(),
|
|
);
|
|
return imageMedia.mediaFile;
|
|
}
|
|
|
|
String? _getFirstVideoUrl() {
|
|
if (!_hasVideo()) return null;
|
|
final videoMedia = widget.post.mediaFiles!.firstWhere(
|
|
(media) => media.mediaType?.toLowerCase().contains('video') ?? false,
|
|
orElse: () => MediaFile(),
|
|
);
|
|
return videoMedia.mediaFile;
|
|
}
|
|
|
|
String? _getFirstVideoThumbnail() {
|
|
if (!_hasVideo()) return null;
|
|
final videoMedia = widget.post.mediaFiles!.firstWhere(
|
|
(media) => media.mediaType?.toLowerCase().contains('video') ?? false,
|
|
orElse: () => MediaFile(),
|
|
);
|
|
return videoMedia.thumbnailFile;
|
|
}
|
|
|
|
String _formatDateTime(DateTime? dateTime) {
|
|
if (dateTime == null) return '';
|
|
final now = DateTime.now();
|
|
final difference = now.difference(dateTime);
|
|
|
|
if (difference.inDays > 7) {
|
|
return DateFormat('MMM d, yyyy').format(dateTime);
|
|
} 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}min ago';
|
|
} else {
|
|
return 'Just now';
|
|
}
|
|
}
|
|
|
|
void _votePoll(int optionId) async {
|
|
developer.log("Vote poll function triggered for option: $optionId");
|
|
//...............................................................................
|
|
// Check if user has already voted or poll has expired....................................
|
|
//...............................................................................
|
|
// if (_hasUserVotedOnAnyOption()) {
|
|
// developer.log("User already voted on this poll");
|
|
// customSnackbar(
|
|
// title: "Voted",
|
|
// message: "You have already voted on this poll",
|
|
// duration: 1,
|
|
// color: Colors.red.withValues(alpha: 0.7),
|
|
// textColor: Colors.white,
|
|
// );
|
|
// return;
|
|
// }
|
|
//...............................................................................
|
|
// Checking if Poll is expired or not.............................................
|
|
//...............................................................................
|
|
// if (_isPollExpired()) {
|
|
// customSnackbar(
|
|
// title: "Expired",
|
|
// message: "Poll expired, tou cannot vote now",
|
|
// duration: 1,
|
|
// color: Colors.red.withValues(alpha: 0.7),
|
|
// textColor: Colors.white,
|
|
// );
|
|
// return;
|
|
// }
|
|
//...............................................................................
|
|
// Check if already votted that particular POLL Just now
|
|
//...............................................................................
|
|
// if (_isVoting) {
|
|
// developer.log("Already in voting process");
|
|
// return;
|
|
// }
|
|
//...............................................................................
|
|
// Just update the selected option immediately - no loading indicator or animation
|
|
setState(() {
|
|
_selectedPollOptionId = optionId.toString();
|
|
_isVoting = true;
|
|
});
|
|
|
|
developer.log("Setting selected option: $_selectedPollOptionId");
|
|
|
|
try {
|
|
// Call the API through the parent callback
|
|
if (widget.onPollOptionSelected != null &&
|
|
widget.post.poll?.pollPostId != null) {
|
|
developer.log(
|
|
"Calling onPollOptionSelected callback with pollId: ${widget.post.poll!.pollPostId}, optionId: $optionId",
|
|
);
|
|
|
|
// Call the API - the parent will le updating the post data
|
|
widget.onPollOptionSelected!(widget.post.poll!.pollPostId!, optionId);
|
|
}
|
|
} catch (e) {
|
|
// Reset if there's an error
|
|
developer.log("Error during voting: $e");
|
|
setState(() {
|
|
_selectedPollOptionId = null;
|
|
_isVoting = false;
|
|
});
|
|
|
|
customSnackbar(
|
|
title: "Error",
|
|
message: 'Failed to submit your vote. Please try after some time.',
|
|
duration: 1,
|
|
color: Colors.red.withValues(alpha: 0.7),
|
|
textColor: Colors.white,
|
|
);
|
|
}
|
|
}
|
|
|
|
//...............................................................................
|
|
// Check if user has already voted or poll has expired....................................
|
|
//...............................................................................
|
|
// bool _hasUserVoted() {
|
|
// return _hasUserVotedOnAnyOption() || _isPollExpired();
|
|
// }
|
|
//...............................................................................
|
|
//...............................................................................
|
|
// Check if content needs expansion
|
|
bool _shouldShowSeeMore() {
|
|
final content = widget.post.content;
|
|
return content != null && content.length > _contentPreviewLength;
|
|
}
|
|
|
|
// Get content to display based on expansion state
|
|
String _getDisplayContent() {
|
|
final content = widget.post.content ?? '';
|
|
if (!_shouldShowSeeMore() || _isContentExpanded) {
|
|
return content;
|
|
}
|
|
return content.substring(0, _contentPreviewLength);
|
|
}
|
|
|
|
Widget _buildProfileAvatar() {
|
|
return InkWell(
|
|
onTap: widget.onProfilePressed,
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.circular(30.r),
|
|
child:
|
|
widget.post.profilePicture != null &&
|
|
widget.post.profilePicture!.trim().isNotEmpty
|
|
? CachedNetworkImage(
|
|
imageUrl: widget.post.profilePicture!,
|
|
width: 50.r,
|
|
height: 50.r,
|
|
fit: BoxFit.cover,
|
|
filterQuality: FilterQuality.low,
|
|
placeholder: (context, url) => buildAvatarPlaceholder(),
|
|
errorWidget:
|
|
(context, url, error) => buildAvatarPlaceholder(),
|
|
)
|
|
: buildAvatarPlaceholder(),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget buildAvatarPlaceholder() {
|
|
return CircleAvatar(
|
|
backgroundColor: lightGreyColor,
|
|
radius: isTablet ? 25.sp : 20.sp,
|
|
child: Icon(Icons.person, size: 30, color: Color(darkGreyColor)),
|
|
);
|
|
}
|
|
|
|
Widget _getReactionIcon() {
|
|
if (widget.post.postReactionTypeId == null) {
|
|
return Image.asset(
|
|
AssetConstants.likeIcon,
|
|
height: isTablet ? 25.h : 20.h,
|
|
width: isTablet ? 25.w : 20.w,
|
|
);
|
|
}
|
|
|
|
switch (widget.post.postReactionTypeId) {
|
|
case ApiEnum.highFive:
|
|
return Image.asset(
|
|
AssetConstants.highFiveIcon,
|
|
height: isTablet ? 25.h : 20.h,
|
|
width: isTablet ? 25.w : 20.w,
|
|
);
|
|
case ApiEnum.heart:
|
|
return Image.asset(
|
|
AssetConstants.heartReactionicon,
|
|
height: isTablet ? 25.h : 20.h,
|
|
width: isTablet ? 25.w : 20.w,
|
|
);
|
|
case ApiEnum.beastMode:
|
|
return Image.asset(
|
|
AssetConstants.beastModeIcon,
|
|
height: isTablet ? 25.h : 20.h,
|
|
width: isTablet ? 25.w : 20.w,
|
|
);
|
|
default:
|
|
return Image.asset(
|
|
AssetConstants.likeIcon,
|
|
height: isTablet ? 25.h : 20.h,
|
|
width: isTablet ? 25.w : 20.w,
|
|
);
|
|
}
|
|
}
|
|
|
|
List<TextSpan> parseTextWithBoldFormatting(String text) {
|
|
List<TextSpan> spans = [];
|
|
RegExp boldRegex = RegExp(r'\*\*(.*?)\*\*');
|
|
int lastMatchEnd = 0;
|
|
Iterable<RegExpMatch> matches = boldRegex.allMatches(text);
|
|
|
|
for (RegExpMatch match in matches) {
|
|
if (match.start > lastMatchEnd) {
|
|
spans.add(
|
|
TextSpan(
|
|
text: text.substring(lastMatchEnd, match.start),
|
|
style: TextStyle(fontSize: smallSizeText, color: Colors.black),
|
|
),
|
|
);
|
|
}
|
|
|
|
spans.add(
|
|
TextSpan(
|
|
text: match.group(1),
|
|
style: TextStyle(
|
|
fontSize: smallSizeText,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
);
|
|
|
|
lastMatchEnd = match.end;
|
|
}
|
|
|
|
if (lastMatchEnd < text.length) {
|
|
spans.add(
|
|
TextSpan(
|
|
text: text.substring(lastMatchEnd),
|
|
style: TextStyle(fontSize: smallSizeText, color: Colors.black),
|
|
),
|
|
);
|
|
}
|
|
|
|
return spans;
|
|
}
|
|
|
|
String getPostVisibilityText() {
|
|
// Adjust these IDs based on your ApiEnum constants
|
|
switch (widget.post.postVisibilityId) {
|
|
case 1: // Public
|
|
return '🌐 Public';
|
|
case 2: // Private
|
|
return '🔒 Private';
|
|
case 3: // Custom
|
|
return '⚙️ Exclusive';
|
|
default:
|
|
return '🌐 Public'; // Default fallback
|
|
}
|
|
}
|
|
|
|
// Build content with see more/see less functionality
|
|
Widget _buildContentSection() {
|
|
if (widget.post.content == null || widget.post.content!.isEmpty) {
|
|
return SizedBox.shrink();
|
|
}
|
|
|
|
return Padding(
|
|
padding: EdgeInsets.all(12.w),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
RichText(
|
|
text: TextSpan(
|
|
children: parseTextWithBoldFormatting(_getDisplayContent()),
|
|
),
|
|
),
|
|
if (_shouldShowSeeMore()) ...[
|
|
if (!_isContentExpanded)
|
|
Text(
|
|
'...',
|
|
style: TextStyle(fontSize: smallSizeText, color: Colors.black),
|
|
),
|
|
SizedBox(height: 4.h),
|
|
GestureDetector(
|
|
onTap: () {
|
|
setState(() {
|
|
_isContentExpanded = !_isContentExpanded;
|
|
});
|
|
},
|
|
child: Text(
|
|
_isContentExpanded ? 'See less' : 'See more',
|
|
style: TextStyle(
|
|
fontSize: smallSizeText,
|
|
color: Color(primaryColor),
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
super.build(context);
|
|
|
|
return InkWell(
|
|
onTap: widget.onPostTap,
|
|
child: Container(
|
|
color: Colors.white,
|
|
margin: EdgeInsets.only(bottom: 10.h),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
ListTile(
|
|
contentPadding: EdgeInsets.only(left: 10.w, right: 0),
|
|
leading: _buildProfileAvatar(),
|
|
|
|
title: InkWell(
|
|
onTap: widget.onProfilePressed,
|
|
child: Text(
|
|
widget.post.fullName ?? 'Unknown User',
|
|
style: TextStyle(
|
|
fontSize: mediumSizeText,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
subtitle: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
widget.post.userTypeName != null
|
|
? widget.post.userTypeName == ApiEnum.coachUserRole &&
|
|
widget.post.coachTypeName != null
|
|
? Text(
|
|
widget.post.coachTypeName!,
|
|
style: TextStyle(
|
|
fontSize: smallSizeText,
|
|
fontWeight: FontWeight.w600,
|
|
color: greyTextColor1,
|
|
),
|
|
)
|
|
: Text(
|
|
widget.post.userTypeName!,
|
|
style: TextStyle(
|
|
fontSize: smallSizeText,
|
|
fontWeight: FontWeight.w600,
|
|
color: greyTextColor1,
|
|
),
|
|
)
|
|
: Container(),
|
|
Row(
|
|
children: [
|
|
Text(
|
|
_formatDateTime(widget.post.createdAt),
|
|
style: TextStyle(
|
|
fontSize: 10.sp,
|
|
fontWeight: FontWeight.w600,
|
|
color: greyTextColor1,
|
|
),
|
|
),
|
|
if (widget.post.postVisibilityId != null) ...[
|
|
if (widget.isTribeEchoboard == false)
|
|
Text(
|
|
' • ',
|
|
style: TextStyle(
|
|
fontSize: verySmallSizeText,
|
|
color: greyTextColor1,
|
|
),
|
|
),
|
|
if (widget.isTribeEchoboard == false)
|
|
Text(
|
|
getPostVisibilityText(),
|
|
style: TextStyle(
|
|
fontSize: 10.sp,
|
|
fontWeight: FontWeight.w500,
|
|
color: greyTextColor1,
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
],
|
|
),
|
|
trailing:
|
|
widget.isThreeDotButtonEnabled
|
|
? IconButton(
|
|
icon: Icon(Icons.more_vert),
|
|
onPressed: widget.onMorePressed,
|
|
)
|
|
: SizedBox.shrink(),
|
|
),
|
|
|
|
// Content section with see more/see less
|
|
_buildContentSection(),
|
|
|
|
if (_hasImage())
|
|
GestureDetector(
|
|
onTap: () {
|
|
// Navigate to zoom view
|
|
Navigator.of(context).push(
|
|
MaterialPageRoute(
|
|
builder:
|
|
(context) => ImageZoomWidget(
|
|
imageUrl: _getFirstImageUrl()!,
|
|
heroTag:
|
|
'zoom_image_${widget.post.postId}${DateTime.now().millisecondsSinceEpoch}',
|
|
),
|
|
),
|
|
);
|
|
},
|
|
child: Hero(
|
|
tag:
|
|
'post_image_${widget.post.postId}${DateTime.now().millisecondsSinceEpoch}',
|
|
child: CachedNetworkImage(
|
|
imageUrl: _getFirstImageUrl()!,
|
|
width: double.infinity,
|
|
height: isTablet ? 300.h : 250.h,
|
|
fit: BoxFit.cover,
|
|
cacheManager: CustomCacheManager.instance,
|
|
filterQuality: FilterQuality.low,
|
|
placeholder:
|
|
(context, url) => Shimmer.fromColors(
|
|
baseColor: Colors.grey.shade300,
|
|
highlightColor: Colors.grey.shade100,
|
|
child: Container(
|
|
width: double.infinity,
|
|
height: isTablet ? 300.h : 250.h,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
errorWidget:
|
|
(context, url, error) => Container(
|
|
height: isTablet ? 300.h : 250.h,
|
|
color: Colors.grey[200],
|
|
child: Center(child: Icon(Icons.error)),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
// Optimized video section - lazy loading with thumbnail
|
|
if (_hasVideo())
|
|
VideoCard(
|
|
category: "",
|
|
videoUrl: _getFirstVideoUrl()!,
|
|
isMyVideoScreen: false,
|
|
title: null,
|
|
thumbnailUrl: _getFirstVideoThumbnail(),
|
|
),
|
|
|
|
if (_hasPoll())
|
|
Stack(
|
|
children: [
|
|
Padding(
|
|
padding: EdgeInsets.only(
|
|
left: 12.w,
|
|
right: 18.w,
|
|
top: 10.w,
|
|
bottom: 10.w,
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Expanded(
|
|
child: Text(
|
|
widget.post.poll!.question ?? 'Poll Question',
|
|
style: TextStyle(
|
|
fontSize: mediumSizeText,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
if (widget.post.poll?.totalPollVotes != null)
|
|
Text(
|
|
'${widget.post.poll!.totalPollVotes} votes',
|
|
style: TextStyle(
|
|
fontSize: smallSizeText,
|
|
color: Colors.grey[600],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 12.h),
|
|
if (widget.post.poll!.options != null)
|
|
...widget.post.poll!.options!.map(
|
|
(option) => _buildPollOption(option),
|
|
),
|
|
// Expired and Expiring date is showing.............................................................................
|
|
// if (widget.post.poll!.expiryDate != null)
|
|
// Padding(
|
|
// padding: EdgeInsets.only(top: 12.h),
|
|
// child: Row(
|
|
// children: [
|
|
// Icon(
|
|
// _isPollExpired()
|
|
// ? Icons.lock_clock
|
|
// : Icons.access_time,
|
|
// size: 16.sp,
|
|
// color:
|
|
// _isPollExpired()
|
|
// ? Colors.red
|
|
// : Colors.grey[600],
|
|
// ),
|
|
// SizedBox(width: 4.w),
|
|
// Text(
|
|
// _isPollExpired()
|
|
// ? 'Poll ended: ${_formatDateTime(widget.post.poll!.expiryDate)}'
|
|
// : 'Poll ends: ${_formatDateTime(widget.post.poll!.expiryDate)}',
|
|
// style: TextStyle(
|
|
// fontSize: verySmallSizeText,
|
|
// color:
|
|
// _isPollExpired()
|
|
// ? Colors.red
|
|
// : Colors.grey[600],
|
|
// ),
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// ),
|
|
//..............................................................................................
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 2.w, vertical: 8.h),
|
|
child: Row(
|
|
children: [
|
|
Row(
|
|
children: [
|
|
IconButton(
|
|
key: widget.likeButtonKey,
|
|
icon: _getReactionIcon(),
|
|
onPressed: () {
|
|
widget.onLikePressed();
|
|
},
|
|
onLongPress: () {
|
|
widget.onLikeLongPressed();
|
|
},
|
|
),
|
|
Text(
|
|
'${widget.post.totalPostReactions ?? 0}',
|
|
style: TextStyle(
|
|
fontSize: smallSizeText,
|
|
color:
|
|
widget.post.postReactionTypeId != null
|
|
? Colors.black
|
|
: null,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(width: 10.w),
|
|
Row(
|
|
children: [
|
|
IconButton(
|
|
icon: Image.asset(
|
|
AssetConstants.commentsLogo,
|
|
height: 20.h,
|
|
width: 20.w,
|
|
),
|
|
onPressed: widget.onCommentPressed,
|
|
),
|
|
Text(
|
|
'${widget.post.totalPostComments ?? 0}',
|
|
style: TextStyle(fontSize: smallSizeText),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(width: 10.w),
|
|
Row(
|
|
children: [
|
|
IconButton(
|
|
icon: Image.asset(
|
|
AssetConstants.shareLogo,
|
|
height: 20.h,
|
|
width: 20.w,
|
|
),
|
|
onPressed: widget.onSharePressed,
|
|
),
|
|
// Text('0', style: TextStyle(fontSize: smallSizeText)),
|
|
],
|
|
),
|
|
SizedBox(width: 20.w),
|
|
],
|
|
),
|
|
),
|
|
Divider(height: 1.h, thickness: 1.h, color: Colors.grey[200]),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildPollOption(Option option) {
|
|
final totalVotes = widget.post.poll?.totalPollVotes ?? 0;
|
|
final optionVotes = option.totalOptionPollVotes ?? 0;
|
|
|
|
double percentage = 0.0;
|
|
if (totalVotes > 0 && optionVotes >= 0) {
|
|
percentage = optionVotes / totalVotes;
|
|
if (percentage.isNaN || percentage.isInfinite) {
|
|
percentage = 0.0;
|
|
}
|
|
percentage = percentage.clamp(0.0, 1.0);
|
|
}
|
|
|
|
final bool isSelected =
|
|
(option.optionId != null &&
|
|
_selectedPollOptionId == option.optionId.toString()) ||
|
|
(option.hasUserVoted == true && _selectedPollOptionId == null);
|
|
//................................................................
|
|
// final isDisabled = _isPollExpired() || _hasUserVoted();
|
|
//................................................................
|
|
return Padding(
|
|
padding: EdgeInsets.symmetric(vertical: 4.h),
|
|
child: InkWell(
|
|
//........ Checking Expired or not................................
|
|
// onTap:
|
|
// isDisabled
|
|
// ? null
|
|
// : () {
|
|
// developer.log("Poll option tapped: ${option.optionId}");
|
|
// if (option.optionId != null) {
|
|
// _votePoll(option.optionId!);
|
|
// }
|
|
// },
|
|
//................................................................
|
|
onTap: () {
|
|
developer.log("Poll option tapped: ${option.optionId}");
|
|
if (option.optionId != null) {
|
|
_votePoll(option.optionId!);
|
|
}
|
|
},
|
|
borderRadius: BorderRadius.circular(8.r),
|
|
child: Padding(
|
|
padding: EdgeInsets.symmetric(vertical: 2.h),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Container(
|
|
height: isTablet ? 45.h : 40.h,
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(8.r),
|
|
border: Border.all(color: containerBorderColor, width: 2),
|
|
),
|
|
child: Stack(
|
|
children: [
|
|
Positioned.fill(
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.circular(6.r),
|
|
child: LinearProgressIndicator(
|
|
value: percentage,
|
|
backgroundColor: Colors.white,
|
|
valueColor: AlwaysStoppedAnimation<Color>(
|
|
isSelected
|
|
? Color(primaryColor)
|
|
: Color(primaryColor).withValues(alpha: 0.3),
|
|
),
|
|
minHeight: isTablet ? 45.h : 40.h,
|
|
),
|
|
),
|
|
),
|
|
// Content overlay
|
|
Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 12.w),
|
|
child: Center(
|
|
child: Row(
|
|
children: [
|
|
if (isSelected) ...[
|
|
Icon(
|
|
Icons.check_circle,
|
|
color: Colors.white,
|
|
size: regularSizeText,
|
|
),
|
|
SizedBox(width: 8.w),
|
|
],
|
|
Expanded(
|
|
child: Text(
|
|
option.optionLabel ?? '',
|
|
style: TextStyle(
|
|
fontSize: smallSizeText,
|
|
color:
|
|
isSelected
|
|
? Colors.black
|
|
: Colors.black87,
|
|
fontWeight:
|
|
isSelected
|
|
? FontWeight.w500
|
|
: FontWeight.normal,
|
|
),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
Text(
|
|
'${(percentage * 100).toInt()}% ($optionVotes)',
|
|
style: TextStyle(
|
|
fontSize: smallSizeText,
|
|
color:
|
|
isSelected ? Colors.black : Colors.black87,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|