added comment section
This commit is contained in:
parent
08d6e4e9e9
commit
bdddd9768e
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M9.50978 4.22989L18.0698 8.50989C21.9098 10.4299 21.9098 13.5699 18.0698 15.4899L9.50978 19.7699C3.74978 22.6499 1.39978 20.2899 4.27978 14.5399L5.14978 12.8099C5.36978 12.3699 5.36978 11.6399 5.14978 11.1999L4.27978 9.45989C1.39978 3.70989 3.75978 1.34989 9.50978 4.22989Z" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M5.43994 12H10.8399" stroke="#292D32" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 587 B |
|
|
@ -420,6 +420,9 @@ class $AssetsIconsGen {
|
||||||
SvgGenImage get selectedList =>
|
SvgGenImage get selectedList =>
|
||||||
const SvgGenImage('assets/icons/selected list.svg');
|
const SvgGenImage('assets/icons/selected list.svg');
|
||||||
|
|
||||||
|
/// File path: assets/icons/send.svg
|
||||||
|
SvgGenImage get send => const SvgGenImage('assets/icons/send.svg');
|
||||||
|
|
||||||
/// File path: assets/icons/shield.svg
|
/// File path: assets/icons/shield.svg
|
||||||
SvgGenImage get shield => const SvgGenImage('assets/icons/shield.svg');
|
SvgGenImage get shield => const SvgGenImage('assets/icons/shield.svg');
|
||||||
|
|
||||||
|
|
@ -601,6 +604,7 @@ class $AssetsIconsGen {
|
||||||
routing,
|
routing,
|
||||||
searchNormal,
|
searchNormal,
|
||||||
selectedList,
|
selectedList,
|
||||||
|
send,
|
||||||
shield,
|
shield,
|
||||||
shoppingCart,
|
shoppingCart,
|
||||||
slide2,
|
slide2,
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:lba/gen/assets.gen.dart';
|
import 'package:lba/gen/assets.gen.dart';
|
||||||
import 'package:lba/res/colors.dart';
|
import 'package:lba/res/colors.dart';
|
||||||
import 'package:lba/widgets/buildWarpedInfo.dart';
|
import 'package:lba/widgets/buildWarpedInfo.dart';
|
||||||
|
import 'package:lba/widgets/compact_review_input_widget.dart';
|
||||||
|
import 'package:lba/widgets/leave_review_widget.dart';
|
||||||
import 'package:lba/widgets/orderType.dart';
|
import 'package:lba/widgets/orderType.dart';
|
||||||
import 'package:lba/widgets/price_reserve_widget.dart';
|
import 'package:lba/widgets/price_reserve_widget.dart';
|
||||||
import 'package:lba/widgets/rate.dart';
|
import 'package:lba/widgets/rate.dart';
|
||||||
|
|
@ -156,8 +158,10 @@ class _ItemState extends State<Item> with TickerProviderStateMixin {
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.title,
|
widget.title,
|
||||||
style:
|
style: const TextStyle(
|
||||||
const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
0,
|
0,
|
||||||
|
|
@ -213,13 +217,21 @@ class _ItemState extends State<Item> with TickerProviderStateMixin {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 15),
|
const SizedBox(height: 15),
|
||||||
_buildAnimatedWidget(
|
_buildAnimatedWidget(
|
||||||
buildWrappedInfo("Dimensions:", widget.dimensions), 4),
|
buildWrappedInfo("Dimensions:", widget.dimensions),
|
||||||
|
4,
|
||||||
|
),
|
||||||
_buildAnimatedWidget(buildWrappedInfo("Colour:", widget.colour), 5),
|
_buildAnimatedWidget(buildWrappedInfo("Colour:", widget.colour), 5),
|
||||||
_buildAnimatedWidget(
|
_buildAnimatedWidget(
|
||||||
buildWrappedInfo("Material:", widget.material), 6),
|
buildWrappedInfo("Material:", widget.material),
|
||||||
|
6,
|
||||||
|
),
|
||||||
_buildAnimatedWidget(
|
_buildAnimatedWidget(
|
||||||
buildWrappedInfo("Description:", widget.description), 7),
|
buildWrappedInfo("Description:", widget.description),
|
||||||
const SizedBox(height: 30),
|
7,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
const ReviewComposerWidget(),
|
||||||
|
const SizedBox(height: 15),
|
||||||
const Center(
|
const Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
"Top reviews from the United Arab Emirates",
|
"Top reviews from the United Arab Emirates",
|
||||||
|
|
@ -238,8 +250,9 @@ class _ItemState extends State<Item> with TickerProviderStateMixin {
|
||||||
position: Tween<Offset>(
|
position: Tween<Offset>(
|
||||||
begin: const Offset(-1, 0),
|
begin: const Offset(-1, 0),
|
||||||
end: Offset.zero,
|
end: Offset.zero,
|
||||||
).animate(CurvedAnimation(
|
).animate(
|
||||||
parent: animation, curve: Curves.easeInOut)),
|
CurvedAnimation(parent: animation, curve: Curves.easeInOut),
|
||||||
|
),
|
||||||
child: SizeTransition(
|
child: SizeTransition(
|
||||||
sizeFactor: animation,
|
sizeFactor: animation,
|
||||||
axisAlignment: -1,
|
axisAlignment: -1,
|
||||||
|
|
@ -258,17 +271,20 @@ class _ItemState extends State<Item> with TickerProviderStateMixin {
|
||||||
review['userLiked'] = null;
|
review['userLiked'] = null;
|
||||||
} else {
|
} else {
|
||||||
if (review['userLiked'] == false) {
|
if (review['userLiked'] == false) {
|
||||||
review['noCount'] = (review['noCount'] as int) - 1;
|
review['noCount'] =
|
||||||
|
(review['noCount'] as int) - 1;
|
||||||
}
|
}
|
||||||
review['userLiked'] = true;
|
review['userLiked'] = true;
|
||||||
review['yesCount'] = (review['yesCount'] as int) + 1;
|
review['yesCount'] =
|
||||||
|
(review['yesCount'] as int) + 1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (review['userLiked'] == false) {
|
if (review['userLiked'] == false) {
|
||||||
review['userLiked'] = null;
|
review['userLiked'] = null;
|
||||||
} else {
|
} else {
|
||||||
if (review['userLiked'] == true) {
|
if (review['userLiked'] == true) {
|
||||||
review['yesCount'] = (review['yesCount'] as int) - 1;
|
review['yesCount'] =
|
||||||
|
(review['yesCount'] as int) - 1;
|
||||||
}
|
}
|
||||||
review['userLiked'] = false;
|
review['userLiked'] = false;
|
||||||
review['noCount'] = (review['noCount'] as int) + 1;
|
review['noCount'] = (review['noCount'] as int) + 1;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,604 @@
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
import 'package:lba/gen/assets.gen.dart';
|
||||||
|
import 'package:lba/res/colors.dart';
|
||||||
|
|
||||||
|
class ReviewComposerWidget extends StatefulWidget {
|
||||||
|
const ReviewComposerWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ReviewComposerWidget> createState() => _ReviewComposerWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ReviewComposerWidgetState extends State<ReviewComposerWidget>
|
||||||
|
with TickerProviderStateMixin {
|
||||||
|
double _rating = 0;
|
||||||
|
final TextEditingController _reviewController = TextEditingController();
|
||||||
|
XFile? _image;
|
||||||
|
final FocusNode _focusNode = FocusNode();
|
||||||
|
bool _isFocused = false;
|
||||||
|
|
||||||
|
late AnimationController _focusController;
|
||||||
|
late AnimationController _buttonController;
|
||||||
|
late AnimationController _starController;
|
||||||
|
late AnimationController _gradientController;
|
||||||
|
late Animation<double> _gradientAnimation;
|
||||||
|
|
||||||
|
late List<AnimationController> _starControllers;
|
||||||
|
late List<Animation<double>> _starAnimations;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_focusController = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
);
|
||||||
|
_buttonController = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: const Duration(milliseconds: 400),
|
||||||
|
);
|
||||||
|
_starController = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: const Duration(milliseconds: 600),
|
||||||
|
);
|
||||||
|
_gradientController = AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: const Duration(milliseconds: 800),
|
||||||
|
);
|
||||||
|
|
||||||
|
_gradientAnimation = Tween<double>(begin: 0, end: 1).animate(
|
||||||
|
CurvedAnimation(parent: _gradientController, curve: Curves.easeInOut),
|
||||||
|
);
|
||||||
|
|
||||||
|
_starControllers = List.generate(
|
||||||
|
5,
|
||||||
|
(index) => AnimationController(
|
||||||
|
vsync: this,
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
_starAnimations =
|
||||||
|
_starControllers
|
||||||
|
.map(
|
||||||
|
(controller) => Tween<double>(begin: 0.0, end: 1.0).animate(
|
||||||
|
CurvedAnimation(parent: controller, curve: Curves.elasticOut),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
_focusNode.addListener(() {
|
||||||
|
setState(() {
|
||||||
|
_isFocused = _focusNode.hasFocus;
|
||||||
|
if (_isFocused) {
|
||||||
|
_focusController.forward();
|
||||||
|
} else {
|
||||||
|
_focusController.reverse();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
_reviewController.addListener(() {
|
||||||
|
if (_reviewController.text.isNotEmpty) {
|
||||||
|
_triggerGradientAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_reviewController.text.isNotEmpty || _image != null) {
|
||||||
|
_buttonController.forward();
|
||||||
|
} else {
|
||||||
|
_buttonController.reverse();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _triggerGradientAnimation() {
|
||||||
|
if (!_gradientController.isAnimating) {
|
||||||
|
_gradientController.forward().then((_) {
|
||||||
|
_gradientController.reverse();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _animateStarsSequentially(int targetRating) async {
|
||||||
|
for (var controller in _starControllers) {
|
||||||
|
controller.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < targetRating; i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
await Future.delayed(Duration(milliseconds: 120));
|
||||||
|
}
|
||||||
|
|
||||||
|
_starControllers[i].forward().then((_) {
|
||||||
|
Future.delayed(Duration(milliseconds: 100), () {
|
||||||
|
if (mounted) {
|
||||||
|
_starControllers[i].reverse().then((_) {
|
||||||
|
_starControllers[i].forward();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_reviewController.dispose();
|
||||||
|
_focusNode.dispose();
|
||||||
|
_focusController.dispose();
|
||||||
|
_buttonController.dispose();
|
||||||
|
_starController.dispose();
|
||||||
|
_gradientController.dispose();
|
||||||
|
|
||||||
|
for (var controller in _starControllers) {
|
||||||
|
controller.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AnimatedBuilder(
|
||||||
|
animation: _gradientAnimation,
|
||||||
|
builder: (context, child) {
|
||||||
|
return Transform.scale(
|
||||||
|
scale: 1.0 + (_gradientAnimation.value * 0.005),
|
||||||
|
child: AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 12.0),
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 12, 16, 10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
AppColors.primary.withOpacity(
|
||||||
|
0.05 + (_gradientAnimation.value * 0.03),
|
||||||
|
),
|
||||||
|
AppColors.borderPrimary.withOpacity(
|
||||||
|
0.03 + (_gradientAnimation.value * 0.02),
|
||||||
|
),
|
||||||
|
AppColors.buttonPrimary.withOpacity(
|
||||||
|
0.04 + (_gradientAnimation.value * 0.02),
|
||||||
|
),
|
||||||
|
AppColors.surface,
|
||||||
|
],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
stops: [
|
||||||
|
0.0,
|
||||||
|
0.3 + (_gradientAnimation.value * 0.2),
|
||||||
|
0.7 + (_gradientAnimation.value * 0.1),
|
||||||
|
1.0,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
border: Border.all(
|
||||||
|
color:
|
||||||
|
_isFocused
|
||||||
|
? AppColors.primary.withOpacity(
|
||||||
|
0.6 + (_gradientAnimation.value * 0.2),
|
||||||
|
)
|
||||||
|
: AppColors.divider.withOpacity(
|
||||||
|
0.3 + (_gradientAnimation.value * 0.1),
|
||||||
|
),
|
||||||
|
width: _isFocused ? 1.5 : 1,
|
||||||
|
),
|
||||||
|
boxShadow: [
|
||||||
|
if (_isFocused)
|
||||||
|
BoxShadow(
|
||||||
|
color: AppColors.primary.withOpacity(
|
||||||
|
0.15 + (_gradientAnimation.value * 0.05),
|
||||||
|
),
|
||||||
|
blurRadius: 12 + (_gradientAnimation.value * 4),
|
||||||
|
spreadRadius: 1 + (_gradientAnimation.value * 0.5),
|
||||||
|
offset: const Offset(0, 4),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(
|
||||||
|
0.03 + (_gradientAnimation.value * 0.02),
|
||||||
|
),
|
||||||
|
blurRadius: 4 + (_gradientAnimation.value * 2),
|
||||||
|
spreadRadius: 0,
|
||||||
|
offset: const Offset(0, 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_buildHeader(),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
if (_image != null) _buildImagePreview(),
|
||||||
|
_buildInputSection(),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
_buildActionBar(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildHeader() {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
flex: 3,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
_rating > 0 ? 'Thanks for rating!' : 'Rate Your Experience',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 15,
|
||||||
|
color:
|
||||||
|
_rating > 0 ? AppColors.primary : AppColors.textPrimary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_rating > 0)
|
||||||
|
Text(
|
||||||
|
_getRatingText(_rating),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: AppColors.textSecondary,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Expanded(flex: 2, child: _buildAnimatedStarRating()),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildInputSection() {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color:
|
||||||
|
_isFocused
|
||||||
|
? AppColors.surface.withOpacity(0.7)
|
||||||
|
: AppColors.surface.withOpacity(0.4),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
border:
|
||||||
|
_isFocused
|
||||||
|
? Border.all(color: AppColors.primary.withOpacity(0.25))
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
child: TextField(
|
||||||
|
controller: _reviewController,
|
||||||
|
focusNode: _focusNode,
|
||||||
|
maxLines: null,
|
||||||
|
minLines: 2,
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
height: 1.4,
|
||||||
|
color: AppColors.textPrimary,
|
||||||
|
),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: _getHintText(),
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
color: AppColors.textSecondary.withOpacity(0.6),
|
||||||
|
fontSize: 14,
|
||||||
|
height: 1.4,
|
||||||
|
),
|
||||||
|
border: InputBorder.none,
|
||||||
|
focusedBorder: InputBorder.none,
|
||||||
|
enabledBorder: InputBorder.none,
|
||||||
|
isDense: true,
|
||||||
|
contentPadding: EdgeInsets.zero,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildActionBar() {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [_buildImageButton(), _buildSubmitButton()],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildImageButton() {
|
||||||
|
return AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: _pickImage,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.primary.withOpacity(0.08),
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
border: Border.all(color: AppColors.primary.withOpacity(0.15)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SvgPicture.asset(
|
||||||
|
Assets.icons.galleryAdd.path,
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
colorFilter: ColorFilter.mode(
|
||||||
|
AppColors.primary,
|
||||||
|
BlendMode.srcIn,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
'Photo',
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppColors.primary,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _pickImage() async {
|
||||||
|
final ImagePicker picker = ImagePicker();
|
||||||
|
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
|
||||||
|
if (image != null) {
|
||||||
|
setState(() => _image = image);
|
||||||
|
_buttonController.forward();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _submit() {
|
||||||
|
if (_reviewController.text.isEmpty && _image == null) return;
|
||||||
|
print(
|
||||||
|
'Rating: $_rating, Review: ${_reviewController.text}, Image: ${_image?.path}',
|
||||||
|
);
|
||||||
|
setState(() {
|
||||||
|
_rating = 0;
|
||||||
|
_reviewController.clear();
|
||||||
|
_image = null;
|
||||||
|
});
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSubmitButton() {
|
||||||
|
return ScaleTransition(
|
||||||
|
scale: CurvedAnimation(
|
||||||
|
parent: _buttonController,
|
||||||
|
curve: Curves.elasticOut,
|
||||||
|
),
|
||||||
|
child: FadeTransition(
|
||||||
|
opacity: _buttonController,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [AppColors.primary, AppColors.primary.withOpacity(0.8)],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: AppColors.primary.withOpacity(0.25),
|
||||||
|
blurRadius: 6,
|
||||||
|
offset: const Offset(0, 3),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: _submit,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SvgPicture.asset(
|
||||||
|
Assets.icons.send.path,
|
||||||
|
width: 16,
|
||||||
|
height: 16,
|
||||||
|
colorFilter: ColorFilter.mode(
|
||||||
|
AppColors.surface,
|
||||||
|
BlendMode.srcIn,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
'Post',
|
||||||
|
style: TextStyle(
|
||||||
|
color: AppColors.surface,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontSize: 13,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getRatingText(double rating) {
|
||||||
|
if (rating >= 4.5) return 'Excellent! 🎉';
|
||||||
|
if (rating >= 3.5) return 'Great! 👍';
|
||||||
|
if (rating >= 2.5) return 'Good 👌';
|
||||||
|
if (rating >= 1.5) return 'Fair 😐';
|
||||||
|
return 'Could be better 😔';
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getHintText() {
|
||||||
|
if (_rating >= 4.5) {
|
||||||
|
return 'Tell others what made this experience amazing...';
|
||||||
|
} else if (_rating >= 3.5) {
|
||||||
|
return 'Share what you liked about this experience...';
|
||||||
|
} else if (_rating >= 2.5) {
|
||||||
|
return 'Share your thoughts about this experience...';
|
||||||
|
} else if (_rating >= 1.5) {
|
||||||
|
return 'Help us understand what could be improved...';
|
||||||
|
} else {
|
||||||
|
return 'Share your honest feedback to help others...';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildAnimatedStarRating() {
|
||||||
|
return SizedBox(
|
||||||
|
width: 120,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: List.generate(5, (index) {
|
||||||
|
return AnimatedBuilder(
|
||||||
|
animation: _starAnimations[index],
|
||||||
|
builder: (context, child) {
|
||||||
|
final isActive = index < _rating;
|
||||||
|
final animationValue = _starAnimations[index].value;
|
||||||
|
final scaleValue =
|
||||||
|
isActive ? (1.0 + (animationValue * 0.1)) : 1.0;
|
||||||
|
|
||||||
|
return Transform.scale(
|
||||||
|
scale: scaleValue,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
setState(() => _rating = index + 1.0);
|
||||||
|
_animateStarsSequentially(index + 1);
|
||||||
|
_starController.forward().then(
|
||||||
|
(_) => _starController.reverse(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
child: Container(
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
if (isActive && animationValue > 0.5)
|
||||||
|
Container(
|
||||||
|
width: 14,
|
||||||
|
height: 14,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.green.withOpacity(0.3),
|
||||||
|
blurRadius: 3,
|
||||||
|
spreadRadius: 1,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
SvgPicture.asset(
|
||||||
|
isActive
|
||||||
|
? Assets.icons.starFill.path
|
||||||
|
: Assets.icons.star.path,
|
||||||
|
width: 13,
|
||||||
|
height: 13,
|
||||||
|
// colorFilter: ColorFilter.mode(
|
||||||
|
// isActive
|
||||||
|
// ? AppColors.primary
|
||||||
|
// : AppColors.textSecondary.withOpacity(0.4),
|
||||||
|
// BlendMode.srcIn,
|
||||||
|
// ),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildImagePreview() {
|
||||||
|
return TweenAnimationBuilder<double>(
|
||||||
|
duration: const Duration(milliseconds: 400),
|
||||||
|
tween: Tween(begin: 0.0, end: 1.0),
|
||||||
|
curve: Curves.elasticOut,
|
||||||
|
builder: (context, value, child) {
|
||||||
|
return Transform.scale(
|
||||||
|
scale: value,
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.only(bottom: 10.0),
|
||||||
|
padding: const EdgeInsets.all(3),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColors.surface,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
border: Border.all(
|
||||||
|
color: AppColors.primary.withOpacity(0.15),
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: AppColors.primary.withOpacity(0.08),
|
||||||
|
blurRadius: 4,
|
||||||
|
offset: const Offset(0, 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
child: Image.file(
|
||||||
|
File(_image!.path),
|
||||||
|
width: 60,
|
||||||
|
height: 60,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
top: -1,
|
||||||
|
right: -1,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () => setState(() => _image = null),
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(3),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.red.withOpacity(0.85),
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withOpacity(0.2),
|
||||||
|
blurRadius: 2,
|
||||||
|
offset: const Offset(0, 1),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: const Icon(
|
||||||
|
Icons.close,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -23,7 +23,10 @@ class PriceReserveWidget extends StatelessWidget {
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 3),
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 3,
|
||||||
|
),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppColors.offerTimer,
|
color: AppColors.offerTimer,
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
|
@ -80,7 +83,7 @@ class PriceReserveWidget extends StatelessWidget {
|
||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
|
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 200,
|
width: 150,
|
||||||
height: 50,
|
height: 50,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
|
|
@ -96,7 +99,10 @@ class PriceReserveWidget extends StatelessWidget {
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 20,
|
||||||
|
vertical: 12,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Reserve',
|
'Reserve',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue