From 3dca9fd3834a47493f289d1c4438114e3c3c214e Mon Sep 17 00:00:00 2001 From: amirrezaghabeli Date: Mon, 18 Aug 2025 15:33:50 +0330 Subject: [PATCH] added feedback --- lib/data/repositories/tenders_repository.dart | 17 ++ .../feedback_response/feedback_response.dart | 2 +- .../feedback_response.freezed.dart | 66 +++-- .../feedback_response.g.dart | 6 +- lib/data/services/tenders_service.dart | 31 +++ lib/view_models/tenders_view_model.dart | 232 ++++++------------ .../tenders/widgets/main_tenders_slider.dart | 35 ++- .../widgets/tender_action_buttons_row.dart | 71 +++--- 8 files changed, 231 insertions(+), 229 deletions(-) diff --git a/lib/data/repositories/tenders_repository.dart b/lib/data/repositories/tenders_repository.dart index 31b803b..acc2a2c 100644 --- a/lib/data/repositories/tenders_repository.dart +++ b/lib/data/repositories/tenders_repository.dart @@ -1,4 +1,5 @@ import 'package:tm_app/core/utils/result.dart'; +import 'package:tm_app/data/services/model/feedback_response/feedback_response.dart'; import 'package:tm_app/data/services/model/tenders/tender_dislike_response/tender_dislike_response.dart'; import 'package:tm_app/data/services/model/tenders/tender_like_response/tender_like_response.dart'; import 'package:tm_app/data/services/model/tenders/tender_reject_response/tender_reject_response.dart'; @@ -15,6 +16,22 @@ class TendersRepository { return _tendersService.getTenders(); } + Future> getTenderFeedback({ + required String tenderId, + }) async { + return _tendersService.getTenderFeedback(tenderId: tenderId); + } + + Future> feedback({ + required String tenderId, + required String feedbackType, + }) async { + return _tendersService.feedback( + tenderId: tenderId, + feedbackType: feedbackType, + ); + } + Future> likeTender() async { return _tendersService.likeTender(); } diff --git a/lib/data/services/model/feedback_response/feedback_response.dart b/lib/data/services/model/feedback_response/feedback_response.dart index e9b1af1..727a66f 100644 --- a/lib/data/services/model/feedback_response/feedback_response.dart +++ b/lib/data/services/model/feedback_response/feedback_response.dart @@ -13,7 +13,7 @@ part 'feedback_response.g.dart'; abstract class FeedbackResponse with _$FeedbackResponse { const factory FeedbackResponse({ @JsonKey(name: 'meta') required Meta? meta, - @JsonKey(name: 'data') required List? data, + @JsonKey(name: 'data') required FeedbackData? data, @JsonKey(name: 'error') required ErrorModel? error, required String? message, required bool? success, diff --git a/lib/data/services/model/feedback_response/feedback_response.freezed.dart b/lib/data/services/model/feedback_response/feedback_response.freezed.dart index 978135b..e90d163 100644 --- a/lib/data/services/model/feedback_response/feedback_response.freezed.dart +++ b/lib/data/services/model/feedback_response/feedback_response.freezed.dart @@ -15,7 +15,7 @@ T _$identity(T value) => value; /// @nodoc mixin _$FeedbackResponse { -@JsonKey(name: 'meta') Meta? get meta;@JsonKey(name: 'data') List? get data;@JsonKey(name: 'error') ErrorModel? get error; String? get message; bool? get success; +@JsonKey(name: 'meta') Meta? get meta;@JsonKey(name: 'data') FeedbackData? get data;@JsonKey(name: 'error') ErrorModel? get error; String? get message; bool? get success; /// Create a copy of FeedbackResponse /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -28,12 +28,12 @@ $FeedbackResponseCopyWith get copyWith => _$FeedbackResponseCo @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is FeedbackResponse&&(identical(other.meta, meta) || other.meta == meta)&&const DeepCollectionEquality().equals(other.data, data)&&(identical(other.error, error) || other.error == error)&&(identical(other.message, message) || other.message == message)&&(identical(other.success, success) || other.success == success)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is FeedbackResponse&&(identical(other.meta, meta) || other.meta == meta)&&(identical(other.data, data) || other.data == data)&&(identical(other.error, error) || other.error == error)&&(identical(other.message, message) || other.message == message)&&(identical(other.success, success) || other.success == success)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,meta,const DeepCollectionEquality().hash(data),error,message,success); +int get hashCode => Object.hash(runtimeType,meta,data,error,message,success); @override String toString() { @@ -48,11 +48,11 @@ abstract mixin class $FeedbackResponseCopyWith<$Res> { factory $FeedbackResponseCopyWith(FeedbackResponse value, $Res Function(FeedbackResponse) _then) = _$FeedbackResponseCopyWithImpl; @useResult $Res call({ -@JsonKey(name: 'meta') Meta? meta,@JsonKey(name: 'data') List? data,@JsonKey(name: 'error') ErrorModel? error, String? message, bool? success +@JsonKey(name: 'meta') Meta? meta,@JsonKey(name: 'data') FeedbackData? data,@JsonKey(name: 'error') ErrorModel? error, String? message, bool? success }); -$MetaCopyWith<$Res>? get meta;$ErrorModelCopyWith<$Res>? get error; +$MetaCopyWith<$Res>? get meta;$FeedbackDataCopyWith<$Res>? get data;$ErrorModelCopyWith<$Res>? get error; } /// @nodoc @@ -69,7 +69,7 @@ class _$FeedbackResponseCopyWithImpl<$Res> return _then(_self.copyWith( meta: freezed == meta ? _self.meta : meta // ignore: cast_nullable_to_non_nullable as Meta?,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable -as List?,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable +as FeedbackData?,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable as ErrorModel?,message: freezed == message ? _self.message : message // ignore: cast_nullable_to_non_nullable as String?,success: freezed == success ? _self.success : success // ignore: cast_nullable_to_non_nullable as bool?, @@ -91,6 +91,18 @@ $MetaCopyWith<$Res>? get meta { /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') +$FeedbackDataCopyWith<$Res>? get data { + if (_self.data == null) { + return null; + } + + return $FeedbackDataCopyWith<$Res>(_self.data!, (value) { + return _then(_self.copyWith(data: value)); + }); +}/// Create a copy of FeedbackResponse +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') $ErrorModelCopyWith<$Res>? get error { if (_self.error == null) { return null; @@ -181,7 +193,7 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function(@JsonKey(name: 'meta') Meta? meta, @JsonKey(name: 'data') List? data, @JsonKey(name: 'error') ErrorModel? error, String? message, bool? success)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function(@JsonKey(name: 'meta') Meta? meta, @JsonKey(name: 'data') FeedbackData? data, @JsonKey(name: 'error') ErrorModel? error, String? message, bool? success)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _FeedbackResponse() when $default != null: return $default(_that.meta,_that.data,_that.error,_that.message,_that.success);case _: @@ -202,7 +214,7 @@ return $default(_that.meta,_that.data,_that.error,_that.message,_that.success);c /// } /// ``` -@optionalTypeArgs TResult when(TResult Function(@JsonKey(name: 'meta') Meta? meta, @JsonKey(name: 'data') List? data, @JsonKey(name: 'error') ErrorModel? error, String? message, bool? success) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function(@JsonKey(name: 'meta') Meta? meta, @JsonKey(name: 'data') FeedbackData? data, @JsonKey(name: 'error') ErrorModel? error, String? message, bool? success) $default,) {final _that = this; switch (_that) { case _FeedbackResponse(): return $default(_that.meta,_that.data,_that.error,_that.message,_that.success);case _: @@ -222,7 +234,7 @@ return $default(_that.meta,_that.data,_that.error,_that.message,_that.success);c /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function(@JsonKey(name: 'meta') Meta? meta, @JsonKey(name: 'data') List? data, @JsonKey(name: 'error') ErrorModel? error, String? message, bool? success)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function(@JsonKey(name: 'meta') Meta? meta, @JsonKey(name: 'data') FeedbackData? data, @JsonKey(name: 'error') ErrorModel? error, String? message, bool? success)? $default,) {final _that = this; switch (_that) { case _FeedbackResponse() when $default != null: return $default(_that.meta,_that.data,_that.error,_that.message,_that.success);case _: @@ -237,19 +249,11 @@ return $default(_that.meta,_that.data,_that.error,_that.message,_that.success);c @JsonSerializable() class _FeedbackResponse implements FeedbackResponse { - const _FeedbackResponse({@JsonKey(name: 'meta') required this.meta, @JsonKey(name: 'data') required final List? data, @JsonKey(name: 'error') required this.error, required this.message, required this.success}): _data = data; + const _FeedbackResponse({@JsonKey(name: 'meta') required this.meta, @JsonKey(name: 'data') required this.data, @JsonKey(name: 'error') required this.error, required this.message, required this.success}); factory _FeedbackResponse.fromJson(Map json) => _$FeedbackResponseFromJson(json); @override@JsonKey(name: 'meta') final Meta? meta; - final List? _data; -@override@JsonKey(name: 'data') List? get data { - final value = _data; - if (value == null) return null; - if (_data is EqualUnmodifiableListView) return _data; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(value); -} - +@override@JsonKey(name: 'data') final FeedbackData? data; @override@JsonKey(name: 'error') final ErrorModel? error; @override final String? message; @override final bool? success; @@ -267,12 +271,12 @@ Map toJson() { @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _FeedbackResponse&&(identical(other.meta, meta) || other.meta == meta)&&const DeepCollectionEquality().equals(other._data, _data)&&(identical(other.error, error) || other.error == error)&&(identical(other.message, message) || other.message == message)&&(identical(other.success, success) || other.success == success)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _FeedbackResponse&&(identical(other.meta, meta) || other.meta == meta)&&(identical(other.data, data) || other.data == data)&&(identical(other.error, error) || other.error == error)&&(identical(other.message, message) || other.message == message)&&(identical(other.success, success) || other.success == success)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,meta,const DeepCollectionEquality().hash(_data),error,message,success); +int get hashCode => Object.hash(runtimeType,meta,data,error,message,success); @override String toString() { @@ -287,11 +291,11 @@ abstract mixin class _$FeedbackResponseCopyWith<$Res> implements $FeedbackRespon factory _$FeedbackResponseCopyWith(_FeedbackResponse value, $Res Function(_FeedbackResponse) _then) = __$FeedbackResponseCopyWithImpl; @override @useResult $Res call({ -@JsonKey(name: 'meta') Meta? meta,@JsonKey(name: 'data') List? data,@JsonKey(name: 'error') ErrorModel? error, String? message, bool? success +@JsonKey(name: 'meta') Meta? meta,@JsonKey(name: 'data') FeedbackData? data,@JsonKey(name: 'error') ErrorModel? error, String? message, bool? success }); -@override $MetaCopyWith<$Res>? get meta;@override $ErrorModelCopyWith<$Res>? get error; +@override $MetaCopyWith<$Res>? get meta;@override $FeedbackDataCopyWith<$Res>? get data;@override $ErrorModelCopyWith<$Res>? get error; } /// @nodoc @@ -307,8 +311,8 @@ class __$FeedbackResponseCopyWithImpl<$Res> @override @pragma('vm:prefer-inline') $Res call({Object? meta = freezed,Object? data = freezed,Object? error = freezed,Object? message = freezed,Object? success = freezed,}) { return _then(_FeedbackResponse( meta: freezed == meta ? _self.meta : meta // ignore: cast_nullable_to_non_nullable -as Meta?,data: freezed == data ? _self._data : data // ignore: cast_nullable_to_non_nullable -as List?,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable +as Meta?,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable +as FeedbackData?,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable as ErrorModel?,message: freezed == message ? _self.message : message // ignore: cast_nullable_to_non_nullable as String?,success: freezed == success ? _self.success : success // ignore: cast_nullable_to_non_nullable as bool?, @@ -331,6 +335,18 @@ $MetaCopyWith<$Res>? get meta { /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') +$FeedbackDataCopyWith<$Res>? get data { + if (_self.data == null) { + return null; + } + + return $FeedbackDataCopyWith<$Res>(_self.data!, (value) { + return _then(_self.copyWith(data: value)); + }); +}/// Create a copy of FeedbackResponse +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') $ErrorModelCopyWith<$Res>? get error { if (_self.error == null) { return null; diff --git a/lib/data/services/model/feedback_response/feedback_response.g.dart b/lib/data/services/model/feedback_response/feedback_response.g.dart index 8fd6980..00738c5 100644 --- a/lib/data/services/model/feedback_response/feedback_response.g.dart +++ b/lib/data/services/model/feedback_response/feedback_response.g.dart @@ -13,9 +13,9 @@ _FeedbackResponse _$FeedbackResponseFromJson(Map json) => ? null : Meta.fromJson(json['meta'] as Map), data: - (json['data'] as List?) - ?.map((e) => FeedbackData.fromJson(e as Map)) - .toList(), + json['data'] == null + ? null + : FeedbackData.fromJson(json['data'] as Map), error: json['error'] == null ? null diff --git a/lib/data/services/tenders_service.dart b/lib/data/services/tenders_service.dart index e465212..43d9e8d 100644 --- a/lib/data/services/tenders_service.dart +++ b/lib/data/services/tenders_service.dart @@ -1,6 +1,9 @@ +import 'dart:convert'; + import 'package:dio/dio.dart'; import 'package:tm_app/core/utils/logger.dart'; import 'package:tm_app/core/utils/result.dart'; +import 'package:tm_app/data/services/model/feedback_response/feedback_response.dart'; import 'package:tm_app/data/services/model/tenders/tender_dislike_response/tender_dislike_response.dart'; import 'package:tm_app/data/services/model/tenders/tender_like_response/tender_like_response.dart'; import 'package:tm_app/data/services/model/tenders/tender_reject_response/tender_reject_response.dart'; @@ -23,6 +26,34 @@ class TendersService { return result; } + Future> getTenderFeedback({ + required String tenderId, + }) async { + final result = await _networkManager.makeRequest( + '/api/v1/feedback/tender/$tenderId', + method: 'GET', + (json) => FeedbackResponse.fromJson(json), + ); + return result; + } + + Future> feedback({ + required String tenderId, + required String feedbackType, + }) async { + final data = jsonEncode({ + 'feedback_type': feedbackType, + 'tender_id': tenderId, + }); + final result = await _networkManager.makeRequest( + '/api/v1/feedback', + method: 'POST', + (json) => FeedbackResponse.fromJson(json), + data: data, + ); + return result; + } + Future> likeTender() async { try { // final result = await _networkManager.makeRequest( diff --git a/lib/view_models/tenders_view_model.dart b/lib/view_models/tenders_view_model.dart index 709cd13..1c3d324 100644 --- a/lib/view_models/tenders_view_model.dart +++ b/lib/view_models/tenders_view_model.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:tm_app/data/repositories/tenders_repository.dart'; -import 'package:tm_app/data/services/model/tender/tender_model.dart'; +import 'package:tm_app/data/services/model/feedback_data/feedback_data.dart'; +import 'package:tm_app/data/services/model/feedback_response/feedback_response.dart'; import 'package:tm_app/data/services/model/tenders/tenders_response/tenders_response.dart'; import '../core/utils/result.dart'; @@ -18,13 +19,31 @@ class TendersViewModel with ChangeNotifier { bool _isLoading = false; String? _errorMessage; TendersResponse? _tendersResponse; + FeedbackData? _feedbackData; bool get isLoading => _isLoading; String? get errorMessage => _errorMessage; TendersResponse? get tendersResponse => _tendersResponse; + FeedbackData? get feedbackData => _feedbackData; + + List feedbacks = []; int currentPage = 1; + /// Get feedback for a specific tender + FeedbackData? getFeedbackForTender(String tenderId) { + try { + return feedbacks.firstWhere((f) => f.tender == tenderId); + } on Exception { + return null; + } + } + + /// Check if feedback exists for a specific tender + bool hasFeedbackForTender(String tenderId) { + return feedbacks.any((f) => f.tender == tenderId); + } + Future getTenders() async { _isLoading = true; _errorMessage = null; @@ -34,6 +53,7 @@ class TendersViewModel with ChangeNotifier { switch (result) { case Ok(): _tendersResponse = result.value; + getTenderFeedback(_tendersResponse!.data!.tenders!.first.id!); break; case Error(): _errorMessage = result.error.toString(); @@ -45,166 +65,64 @@ class TendersViewModel with ChangeNotifier { notifyListeners(); } - /// Updates a tender's state in the list - void _updateTenderState( - String tenderId, { - bool? liked, - bool? disliked, - bool? rejected, - bool? submitted, - }) { - // if (_tendersResponse?.tendersData?.tenders != null) { - // final updatedTenders = - // _tendersResponse!.tendersData!.tenders!.map((element) { - // if (element.tenderId == tenderId) { - // return element.copyWith( - // liked: liked, - // disliked: disliked, - // rejected: rejected, - // submitted: submitted, - // ); - // } - // return element; - // }).toList(); + Future getTenderFeedback(String tenderId) async { + // _isLoading = true; + _errorMessage = null; + notifyListeners(); - // _tendersResponse = _tendersResponse!.copyWith( - // tendersData: _tendersResponse!.tendersData!.copyWith( - // tenders: updatedTenders, - // ), - // ); - // } + final result = await _tendersRepository.getTenderFeedback( + tenderId: tenderId, + ); + switch (result) { + case Ok(): + _feedbackData = result.value.data; + // Add or update _feedbackData in feedbacks list by tender id + _addOrUpdateFunction(_feedbackData!); + break; + case Error(): + _errorMessage = result.error.toString(); + break; + } + // _isLoading = false; + notifyListeners(); + _errorMessage = null; + notifyListeners(); } - /// Sets a tender as liked - Future markLiked(TenderModel tender) async { - // _updateTenderState( - // tender.id!, - // liked: true, - // disliked: false, - // rejected: false, - // submitted: tender.submitted, - // ); - // notifyListeners(); - // final result = await _tendersRepository.likeTender(); - // switch (result) { - // case Ok(): - // break; - // case Error(): - // break; - // } + Future feedback({ + required String tenderId, + required String feedbackType, + }) async { + _errorMessage = null; + notifyListeners(); + + final result = await _tendersRepository.feedback( + tenderId: tenderId, + feedbackType: feedbackType, + ); + switch (result) { + case Ok(): + _feedbackData = result.value.data; + if (_feedbackData != null) { + _addOrUpdateFunction(_feedbackData!); + } + + break; + case Error(): + _errorMessage = result.error.toString(); + break; + } + notifyListeners(); + _errorMessage = null; + notifyListeners(); } - /// Sets a tender as disliked - Future markDisliked(TenderModel tender) async { - // _updateTenderState( - // tender.tenderId!, - // disliked: true, - // liked: false, - // rejected: tender.rejected, - // submitted: false, - // ); - // notifyListeners(); - // final result = await _tendersRepository.dislikeTender(); - // switch (result) { - // case Ok(): - // break; - // case Error(): - // break; - // } - } - - /// Sets a tender as rejected - Future markRejected(TenderModel tender) async { - // _updateTenderState( - // tender.tenderId!, - // rejected: true, - // liked: false, - // disliked: tender.disliked, - // submitted: false, - // ); - // notifyListeners(); - // final result = await _tendersRepository.rejectTender(); - // switch (result) { - // case Ok(): - // break; - // case Error(): - // break; - // } - } - - /// Sets a tender as submitted - Future markSubmitted(TenderModel tender) async { - // _updateTenderState( - // tender.tenderId!, - // submitted: true, - // liked: tender.liked, - // disliked: false, - // rejected: false, - // ); - // notifyListeners(); - // final result = await _tendersRepository.submitTender(); - // switch (result) { - // case Ok(): - // break; - // case Error(): - // break; - // } - } - - /// Clears a specific state for a tender - void clearTenderState( - TenderModel tenderModel, { - bool? isLiked, - bool? isDisliked, - bool? isRejected, - bool? isSubmitted, - }) { - // _updateTenderState( - // tenderModel.tenderId!, - // liked: isLiked ?? tenderModel.liked, - // disliked: isDisliked ?? tenderModel.disliked, - // rejected: isRejected ?? tenderModel.rejected, - // submitted: isSubmitted ?? tenderModel.submitted, - // ); - } - - /// Toggles like state for a tender - void toggleLike(TenderModel tender) { - // if (tender.liked == true) { - // clearTenderState(tender, isLiked: false); - // } else { - // markLiked(tender); - // } - // notifyListeners(); - } - - /// Toggles dislike state for a tender - void toggleDislike(TenderModel tender) { - // if (tender.disliked == true) { - // clearTenderState(tender, isDisliked: false); - // } else { - // markDisliked(tender); - // } - // notifyListeners(); - } - - /// Toggles reject state for a tender - void toggleReject(TenderModel tender) { - // if (tender.rejected == true) { - // clearTenderState(tender, isRejected: false); - // } else { - // markRejected(tender); - // } - // notifyListeners(); - } - - /// Toggles submit state for a tender - void toggleSubmit(TenderModel tender) { - // if (tender.submitted == true) { - // clearTenderState(tender, isSubmitted: false); - // } else { - // markSubmitted(tender); - // } - // notifyListeners(); + void _addOrUpdateFunction(FeedbackData feedbackData) { + final index = feedbacks.indexWhere((f) => f.tender == feedbackData.tender); + if (index != -1) { + feedbacks[index] = feedbackData; + } else { + feedbacks.add(feedbackData); + } } } diff --git a/lib/views/tenders/widgets/main_tenders_slider.dart b/lib/views/tenders/widgets/main_tenders_slider.dart index f655d88..e48270c 100644 --- a/lib/views/tenders/widgets/main_tenders_slider.dart +++ b/lib/views/tenders/widgets/main_tenders_slider.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:provider/provider.dart'; import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/data/services/model/tender_data/tender_data.dart'; +import 'package:tm_app/view_models/tenders_view_model.dart'; import '../../../core/constants/assets.dart'; import '../../../core/utils/size_config.dart'; @@ -26,29 +28,45 @@ class _MainTendersSliderState extends State { int currentPage = 1; void _goToPreviousPage() { + if (currentPage == 1) { + return; + } + pageController.previousPage( duration: Duration(milliseconds: 300), curve: Curves.easeInOutCubic, ); setState(() { - if (currentPage == 1) { - return; - } currentPage = currentPage - 1; }); + + // Fetch feedback for the previous tender if not already loaded + final viewModel = context.read(); + final previousTenderId = widget.tenders[currentPage - 2].id!; + if (!viewModel.hasFeedbackForTender(previousTenderId)) { + viewModel.getTenderFeedback(previousTenderId); + } } void _goToNextPage() { + if (currentPage == widget.tenders.length) { + return; + } + pageController.nextPage( duration: Duration(milliseconds: 300), curve: Curves.easeInOutCubic, ); setState(() { - if (currentPage == widget.tenders.length) { - return; - } currentPage = currentPage + 1; }); + + // Fetch feedback for the next tender if not already loaded + final viewModel = context.read(); + final nextTenderId = widget.tenders[currentPage].id!; + if (!viewModel.hasFeedbackForTender(nextTenderId)) { + viewModel.getTenderFeedback(nextTenderId); + } } @override @@ -76,6 +94,11 @@ class _MainTendersSliderState extends State { setState(() { currentPage = index + 1; }); + // Fetch feedback for the new tender if not already loaded + final viewModel = context.read(); + if (!viewModel.hasFeedbackForTender(widget.tenders[index].id!)) { + viewModel.getTenderFeedback(widget.tenders[index].id!); + } }, itemBuilder: (context, index) { final tender = widget.tenders[index]; diff --git a/lib/views/tenders/widgets/tender_action_buttons_row.dart b/lib/views/tenders/widgets/tender_action_buttons_row.dart index 5d760aa..77b2b0a 100644 --- a/lib/views/tenders/widgets/tender_action_buttons_row.dart +++ b/lib/views/tenders/widgets/tender_action_buttons_row.dart @@ -21,24 +21,27 @@ class TenderActionButtonsRow extends StatelessWidget { @override Widget build(BuildContext context) { - final viewModel = context.watch(); - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox(width: 16.0.w()), + return Consumer( + builder: (context, viewModel, child) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox(width: 16.0.w()), - // Dislike button - _dislikeButton(viewModel), - SizedBox(width: 32.0.w()), - //reject button - _rejectButton(viewModel), - SizedBox(width: 32.0.w()), - //submit button - _submitButton(viewModel), - SizedBox(width: 32.0.w()), - // Like button - _likeButton(viewModel), - ], + // Dislike button + _dislikeButton(viewModel), + SizedBox(width: 32.0.w()), + //reject button + _rejectButton(viewModel), + SizedBox(width: 32.0.w()), + //submit button + _submitButton(viewModel), + SizedBox(width: 32.0.w()), + // Like button + _likeButton(viewModel), + ], + ); + }, ); } @@ -154,12 +157,16 @@ class TenderActionButtonsRow extends StatelessWidget { } Widget _dislikeButton(TendersViewModel viewModel) { + // Find feedback for this specific tender + final feedback = viewModel.getFeedbackForTender(tender.id!); + final isDisliked = feedback?.feedbackType == 'dislike'; + return Tooltip( message: AppStrings.dislike, child: InkWell( splashColor: Colors.transparent, onTap: () { - // viewModel.toggleDislike(tender); + viewModel.feedback(tenderId: tender.id!, feedbackType: 'dislike'); }, child: Column( children: [ @@ -169,10 +176,7 @@ class TenderActionButtonsRow extends StatelessWidget { fit: BoxFit.cover, AssetsManager.dislike, colorFilter: ColorFilter.mode( - // tender.disliked ?? false - // ? AppColors.errorColor - // : - AppColors.grey50, + isDisliked ? AppColors.errorColor : AppColors.grey50, BlendMode.srcATop, ), ), @@ -183,11 +187,7 @@ class TenderActionButtonsRow extends StatelessWidget { style: TextStyle( fontSize: 12.0.sp(), fontWeight: FontWeight.w400, - color: - // tender.disliked ?? false - // ? AppColors.errorColor - // : - AppColors.grey50, + color: isDisliked ? AppColors.errorColor : AppColors.grey50, ), ), ], @@ -197,12 +197,16 @@ class TenderActionButtonsRow extends StatelessWidget { } Widget _likeButton(TendersViewModel viewModel) { + // Find feedback for this specific tender + final feedback = viewModel.getFeedbackForTender(tender.id!); + final isLiked = feedback?.feedbackType == 'like'; + return Tooltip( message: AppStrings.like, child: InkWell( splashColor: Colors.transparent, onTap: () { - // viewModel.toggleLike(tender); + viewModel.feedback(tenderId: tender.id!, feedbackType: 'like'); }, child: Column( children: [ @@ -212,10 +216,7 @@ class TenderActionButtonsRow extends StatelessWidget { fit: BoxFit.cover, AssetsManager.like, colorFilter: ColorFilter.mode( - // tender.liked ?? false - // ? AppColors.successColor - // : - AppColors.grey50, + isLiked ? AppColors.successColor : AppColors.grey50, BlendMode.srcATop, ), ), @@ -226,11 +227,7 @@ class TenderActionButtonsRow extends StatelessWidget { style: TextStyle( fontSize: 12.0.sp(), fontWeight: FontWeight.w400, - color: - // tender.liked ?? false - // ? AppColors.successColor - // : - AppColors.grey50, + color: isLiked ? AppColors.successColor : AppColors.grey50, ), ), ],