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

384 lines
13 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:intl/intl.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/models/coach_reviews_response_model.dart';
class ReviewCard extends StatelessWidget {
final UserReviewRating review;
const ReviewCard({super.key, required this.review});
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
padding: EdgeInsets.all(16.sp),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8.r),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.05),
blurRadius: 5,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// User info and rating
Row(
children: [
// Profile image
ClipRRect(
borderRadius: BorderRadius.circular(20.r),
child:
review.profilePicture != null
? Image.network(
review.profilePicture!,
height: 40.r,
width: 40.r,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Image.asset(
AssetConstants.dummyUserImage,
height: 40.r,
width: 40.r,
fit: BoxFit.cover,
);
},
)
: Image.asset(
AssetConstants.dummyUserImage,
height: 40.r,
width: 40.r,
fit: BoxFit.cover,
),
),
SizedBox(width: 12.w),
// User name and date
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
review.fullName ?? 'Anonymous User',
style: TextStyle(
fontSize: mediumSizeText,
fontWeight: FontWeight.bold,
),
),
Text(
DateFormat('dd MMM yyyy').format(review.createdAt!),
style: TextStyle(
fontSize: smallSizeText,
color: Colors.grey,
),
),
],
),
),
// Rating stars
Row(
children: List.generate(
5,
(index) => Icon(
index < (review.rating ?? 0)
? Icons.star
: Icons.star_border,
color:
index < (review.rating ?? 0)
? const Color(primaryColor)
: Colors.grey,
size: 18.sp,
),
),
),
],
),
SizedBox(height: 12.h),
// Review text
Text(
review.reviewDescription ?? 'No comments provided',
style: TextStyle(fontSize: regularSizeText, color: Colors.black87),
),
],
),
);
}
}
class RatingStatsWidget extends StatelessWidget {
final double avgRating;
final int totalReviews;
final String userName;
const RatingStatsWidget({
super.key,
required this.avgRating,
required this.totalReviews,
required this.userName,
});
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(16.sp),
child: Column(
children: [
// Average Rating display
Text(
avgRating.toStringAsFixed(1),
style: TextStyle(
fontSize: 48.sp,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
SizedBox(height: 8.h),
// Star Rating
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(5, (index) {
// Calculate if star should be full, half, or empty
double threshold = index + 0.5;
IconData icon;
if (avgRating >= index + 1) {
icon = Icons.star;
} else if (avgRating >= threshold) {
icon = Icons.star_half;
} else {
icon = Icons.star_border;
}
return Icon(icon, color: const Color(primaryColor), size: 24.sp);
}),
),
SizedBox(height: 8.h),
// Total reviews count
Text(
'$totalReviews ${totalReviews == 1 ? 'Review' : 'Reviews'}',
style: TextStyle(fontSize: mediumSizeText, color: Colors.grey[600]),
),
SizedBox(height: 12.h),
// User name description
Text(
'Ratings & reviews for $userName',
style: TextStyle(fontSize: smallSizeText, color: Colors.grey[700]),
textAlign: TextAlign.center,
),
],
),
);
}
}
class ReviewInputDialog extends StatefulWidget {
final Function(int, String) onSubmit;
const ReviewInputDialog({super.key, required this.onSubmit});
@override
State<ReviewInputDialog> createState() => _ReviewInputDialogState();
}
class _ReviewInputDialogState extends State<ReviewInputDialog> {
int _selectedRating = 0;
final TextEditingController _reviewController = TextEditingController();
final _formKey = GlobalKey<FormState>();
@override
void dispose() {
_reviewController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16.r)),
child: Padding(
padding: EdgeInsets.all(16.sp),
child: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Dialog title
Text(
'Write a Review',
style: TextStyle(
fontSize: largeSizeText,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 16.h),
// Rating selector
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Rating',
style: TextStyle(
fontSize: mediumSizeText,
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 8.h),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
5,
(index) => GestureDetector(
onTap: () {
setState(() {
_selectedRating = index + 1;
});
},
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 4.w),
child: Icon(
index < _selectedRating
? Icons.star
: Icons.star_border,
color:
index < _selectedRating
? const Color(primaryColor)
: Colors.grey,
size: 32.sp,
),
),
),
),
),
if (_selectedRating == 0)
Padding(
padding: EdgeInsets.only(top: 8.h),
child: Text(
'Please select a rating',
style: TextStyle(
color: Colors.red,
fontSize: smallSizeText,
),
),
),
],
),
SizedBox(height: 16.h),
// Review text input
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Your Review',
style: TextStyle(
fontSize: mediumSizeText,
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 8.h),
TextFormField(
controller: _reviewController,
maxLines: 5,
decoration: InputDecoration(
hintText: 'Share your experience...',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.r),
borderSide: BorderSide(color: Color(darkGreyColor)),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.r),
borderSide: BorderSide(color: lightGreyColor),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.r),
borderSide: BorderSide(
color: lightGreyColor,
width: 2,
),
),
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'Please enter your review';
}
return null;
},
),
],
),
SizedBox(height: 24.h),
// Action buttons
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text(
'Cancel',
style: TextStyle(
color: Colors.grey[700],
fontSize: regularSizeText,
),
),
),
SizedBox(width: 8.w),
ElevatedButton(
onPressed: () {
if (_selectedRating == 0) {
setState(() {});
return;
}
if (_formKey.currentState!.validate()) {
widget.onSubmit(
_selectedRating,
_reviewController.text.trim(),
);
Navigator.pop(context);
}
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.black,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.r),
),
padding: EdgeInsets.symmetric(
horizontal: 16.w,
vertical: 12.h,
),
),
child: Text(
'SUBMIT',
style: TextStyle(fontSize: regularSizeText),
),
),
],
),
],
),
),
),
),
);
}
}