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