From 74b09274341b3078c21ef5d47fa43df8287ee37b Mon Sep 17 00:00:00 2001 From: amirrezaghabeli Date: Tue, 16 Sep 2025 10:20:12 +0330 Subject: [PATCH] added request successful page and add animation for feedback of tenders --- lib/core/config/app_config.dart | 2 +- .../pages/request_successful_page.dart | 45 +++++++ .../strings/request_successful_strings.dart | 5 + lib/views/tenders/pages/m_tenders_page.dart | 2 +- .../widgets/animated_feedback_button.dart | 116 +++++++++++++++++ .../widgets/tender_action_buttons_row.dart | 122 ++++++++---------- 6 files changed, 219 insertions(+), 73 deletions(-) create mode 100644 lib/views/request_successful/pages/request_successful_page.dart create mode 100644 lib/views/request_successful/strings/request_successful_strings.dart create mode 100644 lib/views/tenders/widgets/animated_feedback_button.dart diff --git a/lib/core/config/app_config.dart b/lib/core/config/app_config.dart index 60b119d..760274f 100644 --- a/lib/core/config/app_config.dart +++ b/lib/core/config/app_config.dart @@ -8,7 +8,7 @@ class AppConfig { // این متد بر اساس محیط فعال، URL مناسب را برمی‌گرداند. static String get apiBaseUrl { - return 'http://10.0.2.2:8081'; + return 'http://10.0.2.2:8081'; // return '192.168.1.103:8081'; // if (isDevelopment) { // // Handle different platforms for local development diff --git a/lib/views/request_successful/pages/request_successful_page.dart b/lib/views/request_successful/pages/request_successful_page.dart new file mode 100644 index 0000000..2e8b291 --- /dev/null +++ b/lib/views/request_successful/pages/request_successful_page.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:tm_app/core/theme/colors.dart'; +import 'package:tm_app/core/utils/size_config.dart'; +import 'package:tm_app/views/request_successful/strings/request_successful_strings.dart'; + +import '../../shared/base_button.dart'; + +class RequestSuccessfulPage extends StatelessWidget { + const RequestSuccessfulPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Column( + children: [ + SizedBox(height: 124.0.h()), + Container( + width: 64.0.w(), + height: 64.0.h(), + decoration: BoxDecoration( + color: AppColors.green30, + shape: BoxShape.circle, + ), + ), + Text( + RequestSuccessfulStrings.requestSuccessfulTitle, + style: TextStyle( + fontSize: 22.0.sp(), + fontWeight: FontWeight.w500, + color: AppColors.grey80, + ), + ), + SizedBox(height: 124.0.h()), + BaseButton( + text: RequestSuccessfulStrings.backToTendersButton, + textColor: AppColors.white, + borderColor: AppColors.mainBlue, + isEnabled: true, + onPressed: () {}, + ), + ], + ), + ); + } +} diff --git a/lib/views/request_successful/strings/request_successful_strings.dart b/lib/views/request_successful/strings/request_successful_strings.dart new file mode 100644 index 0000000..c281afe --- /dev/null +++ b/lib/views/request_successful/strings/request_successful_strings.dart @@ -0,0 +1,5 @@ +class RequestSuccessfulStrings { + static const String requestSuccessfulTitle = + 'Your request has been successfully registered.'; + static const String backToTendersButton = 'Back to Tenders'; +} diff --git a/lib/views/tenders/pages/m_tenders_page.dart b/lib/views/tenders/pages/m_tenders_page.dart index db43482..397e4a2 100644 --- a/lib/views/tenders/pages/m_tenders_page.dart +++ b/lib/views/tenders/pages/m_tenders_page.dart @@ -64,7 +64,7 @@ class _MobileTendersPageState extends State { } return Padding( - padding: EdgeInsets.symmetric(vertical: 24.0.h()), + padding: EdgeInsets.symmetric(vertical: 18.0.h()), child: MainTendersSlider( tenders: viewModel.tendersResponse?.data?.tenders ?? [], ), diff --git a/lib/views/tenders/widgets/animated_feedback_button.dart b/lib/views/tenders/widgets/animated_feedback_button.dart new file mode 100644 index 0000000..af8a04f --- /dev/null +++ b/lib/views/tenders/widgets/animated_feedback_button.dart @@ -0,0 +1,116 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; + +import '../../../core/utils/size_config.dart'; + +class AnimatedFeedbackButton extends StatefulWidget { + final String iconPath; + final Color activeColor; + final Color inactiveColor; + final bool isActive; + final VoidCallback onTap; + final String tooltip; + + const AnimatedFeedbackButton({ + required this.iconPath, + required this.activeColor, + required this.inactiveColor, + required this.isActive, + required this.onTap, + required this.tooltip, + super.key, + }); + + @override + State createState() => _AnimatedFeedbackButtonState(); +} + +class _AnimatedFeedbackButtonState extends State + with TickerProviderStateMixin { + late AnimationController _clickController; + late Animation _scaleAnimation; + late Animation _rotationAnimation; + + @override + void initState() { + super.initState(); + _clickController = AnimationController( + duration: const Duration(milliseconds: 250), + vsync: this, + ); + + _scaleAnimation = Tween(begin: 1.0, end: 1.2).animate( + CurvedAnimation( + parent: _clickController, + curve: const Interval(0.0, 0.3, curve: Curves.elasticOut), + reverseCurve: const Interval(0.0, 0.3, curve: Curves.elasticOut), + ), + ); + + _rotationAnimation = Tween(begin: 0.0, end: -0.15).animate( + CurvedAnimation( + parent: _clickController, + curve: const Interval(0.0, 0.1, curve: Curves.elasticOut), + reverseCurve: const Interval(0.0, 0.1, curve: Curves.elasticOut), + ), + ); + } + + @override + void dispose() { + _clickController.dispose(); + super.dispose(); + } + + void _handleTap() { + _clickController.forward().then((_) { + _clickController.reverse(); + }); + widget.onTap(); + } + + @override + Widget build(BuildContext context) { + return Tooltip( + message: widget.tooltip, + child: InkWell( + splashColor: Colors.transparent, + onTap: _handleTap, + child: AnimatedBuilder( + animation: _clickController, + builder: (context, child) { + return Transform.scale( + scale: _scaleAnimation.value, + child: Transform.rotate( + angle: _rotationAnimation.value, + child: TweenAnimationBuilder( + tween: ColorTween( + begin: widget.inactiveColor, + end: + widget.isActive + ? widget.activeColor + : widget.inactiveColor, + ), + duration: const Duration(milliseconds: 100), + curve: Curves.easeInOut, + builder: (context, color, child) { + return SvgPicture.asset( + width: 24.0.w(), + height: 24.0.w(), + fit: BoxFit.cover, + widget.iconPath, + colorFilter: ColorFilter.mode( + color ?? widget.inactiveColor, + BlendMode.srcATop, + ), + ); + }, + ), + ), + ); + }, + ), + ), + ); + } +} diff --git a/lib/views/tenders/widgets/tender_action_buttons_row.dart b/lib/views/tenders/widgets/tender_action_buttons_row.dart index 1472c06..02f56b9 100644 --- a/lib/views/tenders/widgets/tender_action_buttons_row.dart +++ b/lib/views/tenders/widgets/tender_action_buttons_row.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; import 'package:provider/provider.dart'; import 'package:tm_app/data/services/model/tender_data/tender_data.dart'; import 'package:tm_app/view_models/tenders_view_model.dart'; @@ -13,6 +12,7 @@ import '../../../core/utils/size_config.dart'; import '../../shared/select_submission_bottom_sheet.dart'; import '../../shared/select_submission_dialog.dart'; import '../strings/tenders_strings.dart'; +import '../widgets/animated_feedback_button.dart'; class TenderActionButtonsRow extends StatelessWidget { final TenderData tender; @@ -200,7 +200,6 @@ class TenderActionButtonsRow extends StatelessWidget { } Widget _dislikeButton(TendersViewModel viewModel) { - // Find feedback for this specific tender if (tender.id == null) { return const SizedBox.shrink(); } @@ -208,46 +207,36 @@ class TenderActionButtonsRow extends StatelessWidget { final feedback = viewModel.getFeedbackForTender(tender.id!); final isDisliked = feedback?.feedbackType == TenderFeedback.disliked.value; - return Tooltip( - message: TendersStrings.dislike, - child: InkWell( - splashColor: Colors.transparent, - onTap: () { - viewModel.toogleFeedback( - tenderId: tender.id!, - feedbackType: TenderFeedback.disliked.value, - ); - }, - child: Column( - children: [ - SvgPicture.asset( - width: 24.0.w(), - height: 24.0.w(), - fit: BoxFit.cover, - AssetsManager.dislike, - colorFilter: ColorFilter.mode( - isDisliked ? AppColors.errorColor : AppColors.grey50, - BlendMode.srcATop, - ), - ), - SizedBox(height: 8.0.h()), - if (!isDesktop) - Text( - TendersStrings.dislike, - style: TextStyle( - fontSize: 12.0.sp(), - fontWeight: FontWeight.w400, - color: isDisliked ? AppColors.errorColor : AppColors.grey50, - ), - ), - ], + return Column( + children: [ + AnimatedFeedbackButton( + iconPath: AssetsManager.dislike, + activeColor: AppColors.errorColor, + inactiveColor: AppColors.grey50, + isActive: isDisliked, + tooltip: TendersStrings.dislike, + onTap: () { + viewModel.toogleFeedback( + tenderId: tender.id!, + feedbackType: TenderFeedback.disliked.value, + ); + }, ), - ), + SizedBox(height: 8.0.h()), + if (!isDesktop) + Text( + TendersStrings.dislike, + style: TextStyle( + fontSize: 12.0.sp(), + fontWeight: FontWeight.w400, + color: isDisliked ? AppColors.errorColor : AppColors.grey50, + ), + ), + ], ); } Widget _likeButton(TendersViewModel viewModel) { - // Find feedback for this specific tender if (tender.id == null) { return const SizedBox.shrink(); } @@ -255,41 +244,32 @@ class TenderActionButtonsRow extends StatelessWidget { final feedback = viewModel.getFeedbackForTender(tender.id!); final isLiked = feedback?.feedbackType == TenderFeedback.liked.value; - return Tooltip( - message: TendersStrings.like, - child: InkWell( - splashColor: Colors.transparent, - onTap: () { - viewModel.toogleFeedback( - tenderId: tender.id!, - feedbackType: TenderFeedback.liked.value, - ); - }, - child: Column( - children: [ - SvgPicture.asset( - width: 24.0.w(), - height: 24.0.w(), - fit: BoxFit.cover, - AssetsManager.like, - colorFilter: ColorFilter.mode( - isLiked ? AppColors.successColor : AppColors.grey50, - BlendMode.srcATop, - ), - ), - SizedBox(height: 8.0.h()), - if (!isDesktop) - Text( - TendersStrings.like, - style: TextStyle( - fontSize: 12.0.sp(), - fontWeight: FontWeight.w400, - color: isLiked ? AppColors.successColor : AppColors.grey50, - ), - ), - ], + return Column( + children: [ + AnimatedFeedbackButton( + iconPath: AssetsManager.like, + activeColor: AppColors.successColor, + inactiveColor: AppColors.grey50, + isActive: isLiked, + tooltip: TendersStrings.like, + onTap: () { + viewModel.toogleFeedback( + tenderId: tender.id!, + feedbackType: TenderFeedback.liked.value, + ); + }, ), - ), + SizedBox(height: 8.0.h()), + if (!isDesktop) + Text( + TendersStrings.like, + style: TextStyle( + fontSize: 12.0.sp(), + fontWeight: FontWeight.w400, + color: isLiked ? AppColors.successColor : AppColors.grey50, + ), + ), + ], ); } }