From 79874c40b18f5d42a78270f3262e158ceccc2707 Mon Sep 17 00:00:00 2001 From: llsajjad Date: Tue, 2 Sep 2025 16:56:42 +0330 Subject: [PATCH] Added liked tenders desigm and data from api --- assets/icons/trash.svg | 7 + lib/core/config/dependencies.dart | 16 + lib/core/constants/assets.dart | 4 + lib/core/routes/app_routes.dart | 13 +- lib/core/routes/app_routes.g.dart | 28 ++ .../liked_tenders_repository.dart | 15 + lib/data/services/api/liked_tenders_api.dart | 4 + lib/data/services/liked_tenders_service.dart | 27 ++ .../liked_tender_feedback.dart | 23 ++ .../liked_tender_feedback.freezed.dart | 322 +++++++++++++++++ .../liked_tender_feedback.g.dart | 35 ++ .../liked_tenders_data.dart | 17 + .../liked_tenders_data.freezed.dart | 312 ++++++++++++++++ .../liked_tenders_data.g.dart | 24 ++ .../liked_tenders_response.dart | 20 ++ .../liked_tenders_response.freezed.dart | 334 ++++++++++++++++++ .../liked_tenders_response.g.dart | 31 ++ lib/gen/assets.gen.dart | 4 + lib/view_models/liked_tenders_view_model.dart | 107 ++++++ lib/views/home/pages/d_home_page.dart | 6 +- lib/views/home/pages/m_home_page.dart | 14 +- lib/views/home/pages/t_home_page.dart | 13 +- .../pages/liked_tenders_desktop_page.dart | 169 +++++++++ .../pages/liked_tenders_mobile_page.dart | 136 +++++++ .../pages/liked_tenders_screen.dart | 20 ++ .../pages/liked_tenders_tablet_page.dart | 166 +++++++++ .../strings/liked_tenders_strings.dart | 15 + .../widgets/liked_tenders_list_item.dart | 148 ++++++++ .../pages/your_tenders_tablet_page.dart | 2 +- .../strings/your_tenders_strings.dart | 1 - .../widgets/tablet_desktop_appbar.dart | 2 +- pubspec.lock | 16 +- 32 files changed, 2028 insertions(+), 23 deletions(-) create mode 100644 assets/icons/trash.svg create mode 100644 lib/data/repositories/liked_tenders_repository.dart create mode 100644 lib/data/services/api/liked_tenders_api.dart create mode 100644 lib/data/services/liked_tenders_service.dart create mode 100644 lib/data/services/model/liked_tender_feedback/liked_tender_feedback.dart create mode 100644 lib/data/services/model/liked_tender_feedback/liked_tender_feedback.freezed.dart create mode 100644 lib/data/services/model/liked_tender_feedback/liked_tender_feedback.g.dart create mode 100644 lib/data/services/model/liked_tenders_data/liked_tenders_data.dart create mode 100644 lib/data/services/model/liked_tenders_data/liked_tenders_data.freezed.dart create mode 100644 lib/data/services/model/liked_tenders_data/liked_tenders_data.g.dart create mode 100644 lib/data/services/model/liked_tenders_response/liked_tenders_response.dart create mode 100644 lib/data/services/model/liked_tenders_response/liked_tenders_response.freezed.dart create mode 100644 lib/data/services/model/liked_tenders_response/liked_tenders_response.g.dart create mode 100644 lib/view_models/liked_tenders_view_model.dart create mode 100644 lib/views/liked_tenders/pages/liked_tenders_desktop_page.dart create mode 100644 lib/views/liked_tenders/pages/liked_tenders_mobile_page.dart create mode 100644 lib/views/liked_tenders/pages/liked_tenders_screen.dart create mode 100644 lib/views/liked_tenders/pages/liked_tenders_tablet_page.dart create mode 100644 lib/views/liked_tenders/strings/liked_tenders_strings.dart create mode 100644 lib/views/liked_tenders/widgets/liked_tenders_list_item.dart diff --git a/assets/icons/trash.svg b/assets/icons/trash.svg new file mode 100644 index 0000000..27b4f76 --- /dev/null +++ b/assets/icons/trash.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/lib/core/config/dependencies.dart b/lib/core/config/dependencies.dart index f4e8a77..d128ccb 100644 --- a/lib/core/config/dependencies.dart +++ b/lib/core/config/dependencies.dart @@ -3,12 +3,15 @@ import 'package:provider/single_child_widget.dart'; import 'package:tm_app/core/network/network_manager.dart'; import 'package:tm_app/core/theme/theme_provider.dart'; import 'package:tm_app/data/repositories/home_repository.dart'; +import 'package:tm_app/data/repositories/liked_tenders_repository.dart'; import 'package:tm_app/data/repositories/tender_detail_repository.dart'; import 'package:tm_app/data/repositories/tenders_repository.dart'; import 'package:tm_app/data/repositories/your_tenders_repository.dart'; import 'package:tm_app/data/services/home_service.dart'; +import 'package:tm_app/data/services/liked_tenders_service.dart'; import 'package:tm_app/data/services/tender_detail_service.dart'; import 'package:tm_app/data/services/your_tenders_service.dart'; +import 'package:tm_app/view_models/liked_tenders_view_model.dart'; import 'package:tm_app/view_models/tender_detail_view_model.dart'; import 'package:tm_app/view_models/tenders_view_model.dart'; import 'package:tm_app/view_models/your_tenders_view_model.dart'; @@ -44,6 +47,9 @@ List get apiClients { Provider( create: (context) => YourTendersService(networkManager: context.read()), ), + Provider( + create: (context) => LikedTendersService(networkManager: context.read()), + ), Provider( create: (context) => TendersService(networkManager: context.read()), ), @@ -67,6 +73,11 @@ List get repositories { (context) => YourTendersRepository(yourTendersService: context.read()), ), + Provider( + create: + (context) => + LikedTendersRepository(likedTendersService: context.read()), + ), Provider( create: (context) => TendersRepository(tendersService: context.read()), ), @@ -106,6 +117,11 @@ List get viewModels { (context) => YourTendersViewModel(yourTendersRepository: context.read()), ), + ChangeNotifierProvider( + create: + (context) => + LikedTendersViewModel(likedTendersRepository: context.read()), + ), ChangeNotifierProvider( create: (context) => ProfileViewModel( diff --git a/lib/core/constants/assets.dart b/lib/core/constants/assets.dart index 3ee2671..7dbc80d 100644 --- a/lib/core/constants/assets.dart +++ b/lib/core/constants/assets.dart @@ -59,4 +59,8 @@ class AssetsManager { //profile page static const sun = 'assets/icons/sun.svg'; static const moon = 'assets/icons/moon.svg'; + + //Liked tenders + static const trash = 'assets/icons/trash.svg'; + } diff --git a/lib/core/routes/app_routes.dart b/lib/core/routes/app_routes.dart index 3ed20a9..401d615 100644 --- a/lib/core/routes/app_routes.dart +++ b/lib/core/routes/app_routes.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:tm_app/views/detail/pages/detail_screen.dart'; import 'package:tm_app/views/home/pages/home_screen.dart'; +import 'package:tm_app/views/liked_tenders/pages/liked_tenders_screen.dart'; import 'package:tm_app/views/login/pages/login_screen.dart'; import '../../views/forgot_password/pages/forgot_password_screen.dart'; @@ -26,7 +27,7 @@ final GlobalKey notificationNavigatorKey = // Main app router final GoRouter appRouter = GoRouter( routes: $appRoutes, - initialLocation: '/notification', + initialLocation: '/login', navigatorKey: rootNavigatorKey, ); @@ -171,6 +172,16 @@ class YourTendersRouteData extends GoRouteData with _$YourTendersRouteData { } } +@TypedGoRoute(path: '/like-tenders') +class LikedTendersRouteData extends GoRouteData with _$LikedTendersRouteData { + const LikedTendersRouteData(); + + @override + Widget build(BuildContext context, GoRouterState state) { + return LikedTendersScreen(); + } +} + @TypedGoRoute(path: '/tender-detail') class TenderDetailRouteData extends GoRouteData with _$TenderDetailRouteData { final String tenderId; diff --git a/lib/core/routes/app_routes.g.dart b/lib/core/routes/app_routes.g.dart index d61ba5a..225597b 100644 --- a/lib/core/routes/app_routes.g.dart +++ b/lib/core/routes/app_routes.g.dart @@ -10,6 +10,7 @@ List get $appRoutes => [ $loginScreenRoute, $appShellRouteData, $yourTendersRouteData, + $likedTendersRouteData, $tenderDetailRouteData, $forgotPasswordRouteData, ]; @@ -200,6 +201,33 @@ mixin _$YourTendersRouteData on GoRouteData { void replace(BuildContext context) => context.replace(location); } +RouteBase get $likedTendersRouteData => GoRouteData.$route( + path: '/like-tenders', + + factory: _$LikedTendersRouteData._fromState, +); + +mixin _$LikedTendersRouteData on GoRouteData { + static LikedTendersRouteData _fromState(GoRouterState state) => + const LikedTendersRouteData(); + + @override + String get location => GoRouteData.$location('/like-tenders'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + RouteBase get $tenderDetailRouteData => GoRouteData.$route( path: '/tender-detail', diff --git a/lib/data/repositories/liked_tenders_repository.dart b/lib/data/repositories/liked_tenders_repository.dart new file mode 100644 index 0000000..7bad344 --- /dev/null +++ b/lib/data/repositories/liked_tenders_repository.dart @@ -0,0 +1,15 @@ +import 'package:tm_app/core/utils/result.dart'; +import 'package:tm_app/data/services/liked_tenders_service.dart'; +import 'package:tm_app/data/services/model/liked_tenders_response/liked_tenders_response.dart'; + +class LikedTendersRepository { + final LikedTendersService likedTendersService; + LikedTendersRepository({required this.likedTendersService}); + + Future> getLikedTenders({ + int offset = 0, + int limit = 20, + }) { + return likedTendersService.getLikedTenders(offset: offset, limit: limit); + } +} diff --git a/lib/data/services/api/liked_tenders_api.dart b/lib/data/services/api/liked_tenders_api.dart new file mode 100644 index 0000000..94d0f34 --- /dev/null +++ b/lib/data/services/api/liked_tenders_api.dart @@ -0,0 +1,4 @@ +class LikedTendersApi { + static const String likedTenders = + '/api/v1/feedback?feedback_type=like'; +} diff --git a/lib/data/services/liked_tenders_service.dart b/lib/data/services/liked_tenders_service.dart new file mode 100644 index 0000000..40f777c --- /dev/null +++ b/lib/data/services/liked_tenders_service.dart @@ -0,0 +1,27 @@ +import 'package:tm_app/core/utils/result.dart'; +import 'package:tm_app/data/services/api/liked_tenders_api.dart'; +import 'package:tm_app/data/services/model/liked_tenders_response/liked_tenders_response.dart'; + +import '../../../core/network/network_manager.dart'; + +class LikedTendersService { + LikedTendersService({required NetworkManager networkManager}) + : _networkManager = networkManager; + + final NetworkManager _networkManager; + +Future> getLikedTenders({ + int offset = 0, + int limit = 20, +}) async { + final uri = '${LikedTendersApi.likedTenders}&limit=$limit&offset=$offset'; + + final result = await _networkManager.makeRequest( + uri, + (json) => LikedTendersResponse.fromJson(json), + method: 'GET', + ); + + return result; +} +} diff --git a/lib/data/services/model/liked_tender_feedback/liked_tender_feedback.dart b/lib/data/services/model/liked_tender_feedback/liked_tender_feedback.dart new file mode 100644 index 0000000..d1fb579 --- /dev/null +++ b/lib/data/services/model/liked_tender_feedback/liked_tender_feedback.dart @@ -0,0 +1,23 @@ +// ignore_for_file: invalid_annotation_target +import 'package:freezed_annotation/freezed_annotation.dart'; +import '../tender_data/tender_data.dart'; + +part 'liked_tender_feedback.freezed.dart'; +part 'liked_tender_feedback.g.dart'; + +@freezed +abstract class LikedTenderFeedback with _$LikedTenderFeedback { + const factory LikedTenderFeedback({ + required String? id, + @JsonKey(name: 'feedback_type') required String? feedbackType, + @JsonKey(name: 'updated_at') required int? updatedAt, + @JsonKey(name: 'created_at') required int? createdAt, + @JsonKey(name: 'tender_id') required String? tenderId, + @JsonKey(name: 'customer_id') required String? customerId, + @JsonKey(name: 'company_id') required String? companyId, + required TenderData? tender, + }) = _LikedTenderFeedback; + + factory LikedTenderFeedback.fromJson(Map json) => + _$LikedTenderFeedbackFromJson(json); +} diff --git a/lib/data/services/model/liked_tender_feedback/liked_tender_feedback.freezed.dart b/lib/data/services/model/liked_tender_feedback/liked_tender_feedback.freezed.dart new file mode 100644 index 0000000..6f4db43 --- /dev/null +++ b/lib/data/services/model/liked_tender_feedback/liked_tender_feedback.freezed.dart @@ -0,0 +1,322 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'liked_tender_feedback.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$LikedTenderFeedback { + + String? get id;@JsonKey(name: 'feedback_type') String? get feedbackType;@JsonKey(name: 'updated_at') int? get updatedAt;@JsonKey(name: 'created_at') int? get createdAt;@JsonKey(name: 'tender_id') String? get tenderId;@JsonKey(name: 'customer_id') String? get customerId;@JsonKey(name: 'company_id') String? get companyId; TenderData? get tender; +/// Create a copy of LikedTenderFeedback +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$LikedTenderFeedbackCopyWith get copyWith => _$LikedTenderFeedbackCopyWithImpl(this as LikedTenderFeedback, _$identity); + + /// Serializes this LikedTenderFeedback to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is LikedTenderFeedback&&(identical(other.id, id) || other.id == id)&&(identical(other.feedbackType, feedbackType) || other.feedbackType == feedbackType)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.tenderId, tenderId) || other.tenderId == tenderId)&&(identical(other.customerId, customerId) || other.customerId == customerId)&&(identical(other.companyId, companyId) || other.companyId == companyId)&&(identical(other.tender, tender) || other.tender == tender)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,feedbackType,updatedAt,createdAt,tenderId,customerId,companyId,tender); + +@override +String toString() { + return 'LikedTenderFeedback(id: $id, feedbackType: $feedbackType, updatedAt: $updatedAt, createdAt: $createdAt, tenderId: $tenderId, customerId: $customerId, companyId: $companyId, tender: $tender)'; +} + + +} + +/// @nodoc +abstract mixin class $LikedTenderFeedbackCopyWith<$Res> { + factory $LikedTenderFeedbackCopyWith(LikedTenderFeedback value, $Res Function(LikedTenderFeedback) _then) = _$LikedTenderFeedbackCopyWithImpl; +@useResult +$Res call({ + String? id,@JsonKey(name: 'feedback_type') String? feedbackType,@JsonKey(name: 'updated_at') int? updatedAt,@JsonKey(name: 'created_at') int? createdAt,@JsonKey(name: 'tender_id') String? tenderId,@JsonKey(name: 'customer_id') String? customerId,@JsonKey(name: 'company_id') String? companyId, TenderData? tender +}); + + +$TenderDataCopyWith<$Res>? get tender; + +} +/// @nodoc +class _$LikedTenderFeedbackCopyWithImpl<$Res> + implements $LikedTenderFeedbackCopyWith<$Res> { + _$LikedTenderFeedbackCopyWithImpl(this._self, this._then); + + final LikedTenderFeedback _self; + final $Res Function(LikedTenderFeedback) _then; + +/// Create a copy of LikedTenderFeedback +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? id = freezed,Object? feedbackType = freezed,Object? updatedAt = freezed,Object? createdAt = freezed,Object? tenderId = freezed,Object? customerId = freezed,Object? companyId = freezed,Object? tender = freezed,}) { + return _then(_self.copyWith( +id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String?,feedbackType: freezed == feedbackType ? _self.feedbackType : feedbackType // ignore: cast_nullable_to_non_nullable +as String?,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable +as int?,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as int?,tenderId: freezed == tenderId ? _self.tenderId : tenderId // ignore: cast_nullable_to_non_nullable +as String?,customerId: freezed == customerId ? _self.customerId : customerId // ignore: cast_nullable_to_non_nullable +as String?,companyId: freezed == companyId ? _self.companyId : companyId // ignore: cast_nullable_to_non_nullable +as String?,tender: freezed == tender ? _self.tender : tender // ignore: cast_nullable_to_non_nullable +as TenderData?, + )); +} +/// Create a copy of LikedTenderFeedback +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$TenderDataCopyWith<$Res>? get tender { + if (_self.tender == null) { + return null; + } + + return $TenderDataCopyWith<$Res>(_self.tender!, (value) { + return _then(_self.copyWith(tender: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [LikedTenderFeedback]. +extension LikedTenderFeedbackPatterns on LikedTenderFeedback { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _LikedTenderFeedback value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _LikedTenderFeedback() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _LikedTenderFeedback value) $default,){ +final _that = this; +switch (_that) { +case _LikedTenderFeedback(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _LikedTenderFeedback value)? $default,){ +final _that = this; +switch (_that) { +case _LikedTenderFeedback() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String? id, @JsonKey(name: 'feedback_type') String? feedbackType, @JsonKey(name: 'updated_at') int? updatedAt, @JsonKey(name: 'created_at') int? createdAt, @JsonKey(name: 'tender_id') String? tenderId, @JsonKey(name: 'customer_id') String? customerId, @JsonKey(name: 'company_id') String? companyId, TenderData? tender)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _LikedTenderFeedback() when $default != null: +return $default(_that.id,_that.feedbackType,_that.updatedAt,_that.createdAt,_that.tenderId,_that.customerId,_that.companyId,_that.tender);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String? id, @JsonKey(name: 'feedback_type') String? feedbackType, @JsonKey(name: 'updated_at') int? updatedAt, @JsonKey(name: 'created_at') int? createdAt, @JsonKey(name: 'tender_id') String? tenderId, @JsonKey(name: 'customer_id') String? customerId, @JsonKey(name: 'company_id') String? companyId, TenderData? tender) $default,) {final _that = this; +switch (_that) { +case _LikedTenderFeedback(): +return $default(_that.id,_that.feedbackType,_that.updatedAt,_that.createdAt,_that.tenderId,_that.customerId,_that.companyId,_that.tender);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? id, @JsonKey(name: 'feedback_type') String? feedbackType, @JsonKey(name: 'updated_at') int? updatedAt, @JsonKey(name: 'created_at') int? createdAt, @JsonKey(name: 'tender_id') String? tenderId, @JsonKey(name: 'customer_id') String? customerId, @JsonKey(name: 'company_id') String? companyId, TenderData? tender)? $default,) {final _that = this; +switch (_that) { +case _LikedTenderFeedback() when $default != null: +return $default(_that.id,_that.feedbackType,_that.updatedAt,_that.createdAt,_that.tenderId,_that.customerId,_that.companyId,_that.tender);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _LikedTenderFeedback implements LikedTenderFeedback { + const _LikedTenderFeedback({required this.id, @JsonKey(name: 'feedback_type') required this.feedbackType, @JsonKey(name: 'updated_at') required this.updatedAt, @JsonKey(name: 'created_at') required this.createdAt, @JsonKey(name: 'tender_id') required this.tenderId, @JsonKey(name: 'customer_id') required this.customerId, @JsonKey(name: 'company_id') required this.companyId, required this.tender}); + factory _LikedTenderFeedback.fromJson(Map json) => _$LikedTenderFeedbackFromJson(json); + +@override final String? id; +@override@JsonKey(name: 'feedback_type') final String? feedbackType; +@override@JsonKey(name: 'updated_at') final int? updatedAt; +@override@JsonKey(name: 'created_at') final int? createdAt; +@override@JsonKey(name: 'tender_id') final String? tenderId; +@override@JsonKey(name: 'customer_id') final String? customerId; +@override@JsonKey(name: 'company_id') final String? companyId; +@override final TenderData? tender; + +/// Create a copy of LikedTenderFeedback +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$LikedTenderFeedbackCopyWith<_LikedTenderFeedback> get copyWith => __$LikedTenderFeedbackCopyWithImpl<_LikedTenderFeedback>(this, _$identity); + +@override +Map toJson() { + return _$LikedTenderFeedbackToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _LikedTenderFeedback&&(identical(other.id, id) || other.id == id)&&(identical(other.feedbackType, feedbackType) || other.feedbackType == feedbackType)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.tenderId, tenderId) || other.tenderId == tenderId)&&(identical(other.customerId, customerId) || other.customerId == customerId)&&(identical(other.companyId, companyId) || other.companyId == companyId)&&(identical(other.tender, tender) || other.tender == tender)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,id,feedbackType,updatedAt,createdAt,tenderId,customerId,companyId,tender); + +@override +String toString() { + return 'LikedTenderFeedback(id: $id, feedbackType: $feedbackType, updatedAt: $updatedAt, createdAt: $createdAt, tenderId: $tenderId, customerId: $customerId, companyId: $companyId, tender: $tender)'; +} + + +} + +/// @nodoc +abstract mixin class _$LikedTenderFeedbackCopyWith<$Res> implements $LikedTenderFeedbackCopyWith<$Res> { + factory _$LikedTenderFeedbackCopyWith(_LikedTenderFeedback value, $Res Function(_LikedTenderFeedback) _then) = __$LikedTenderFeedbackCopyWithImpl; +@override @useResult +$Res call({ + String? id,@JsonKey(name: 'feedback_type') String? feedbackType,@JsonKey(name: 'updated_at') int? updatedAt,@JsonKey(name: 'created_at') int? createdAt,@JsonKey(name: 'tender_id') String? tenderId,@JsonKey(name: 'customer_id') String? customerId,@JsonKey(name: 'company_id') String? companyId, TenderData? tender +}); + + +@override $TenderDataCopyWith<$Res>? get tender; + +} +/// @nodoc +class __$LikedTenderFeedbackCopyWithImpl<$Res> + implements _$LikedTenderFeedbackCopyWith<$Res> { + __$LikedTenderFeedbackCopyWithImpl(this._self, this._then); + + final _LikedTenderFeedback _self; + final $Res Function(_LikedTenderFeedback) _then; + +/// Create a copy of LikedTenderFeedback +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? id = freezed,Object? feedbackType = freezed,Object? updatedAt = freezed,Object? createdAt = freezed,Object? tenderId = freezed,Object? customerId = freezed,Object? companyId = freezed,Object? tender = freezed,}) { + return _then(_LikedTenderFeedback( +id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable +as String?,feedbackType: freezed == feedbackType ? _self.feedbackType : feedbackType // ignore: cast_nullable_to_non_nullable +as String?,updatedAt: freezed == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable +as int?,createdAt: freezed == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable +as int?,tenderId: freezed == tenderId ? _self.tenderId : tenderId // ignore: cast_nullable_to_non_nullable +as String?,customerId: freezed == customerId ? _self.customerId : customerId // ignore: cast_nullable_to_non_nullable +as String?,companyId: freezed == companyId ? _self.companyId : companyId // ignore: cast_nullable_to_non_nullable +as String?,tender: freezed == tender ? _self.tender : tender // ignore: cast_nullable_to_non_nullable +as TenderData?, + )); +} + +/// Create a copy of LikedTenderFeedback +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$TenderDataCopyWith<$Res>? get tender { + if (_self.tender == null) { + return null; + } + + return $TenderDataCopyWith<$Res>(_self.tender!, (value) { + return _then(_self.copyWith(tender: value)); + }); +} +} + +// dart format on diff --git a/lib/data/services/model/liked_tender_feedback/liked_tender_feedback.g.dart b/lib/data/services/model/liked_tender_feedback/liked_tender_feedback.g.dart new file mode 100644 index 0000000..8188d37 --- /dev/null +++ b/lib/data/services/model/liked_tender_feedback/liked_tender_feedback.g.dart @@ -0,0 +1,35 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'liked_tender_feedback.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_LikedTenderFeedback _$LikedTenderFeedbackFromJson(Map json) => + _LikedTenderFeedback( + id: json['id'] as String?, + feedbackType: json['feedback_type'] as String?, + updatedAt: (json['updated_at'] as num?)?.toInt(), + createdAt: (json['created_at'] as num?)?.toInt(), + tenderId: json['tender_id'] as String?, + customerId: json['customer_id'] as String?, + companyId: json['company_id'] as String?, + tender: + json['tender'] == null + ? null + : TenderData.fromJson(json['tender'] as Map), + ); + +Map _$LikedTenderFeedbackToJson( + _LikedTenderFeedback instance, +) => { + 'id': instance.id, + 'feedback_type': instance.feedbackType, + 'updated_at': instance.updatedAt, + 'created_at': instance.createdAt, + 'tender_id': instance.tenderId, + 'customer_id': instance.customerId, + 'company_id': instance.companyId, + 'tender': instance.tender, +}; diff --git a/lib/data/services/model/liked_tenders_data/liked_tenders_data.dart b/lib/data/services/model/liked_tenders_data/liked_tenders_data.dart new file mode 100644 index 0000000..379fe78 --- /dev/null +++ b/lib/data/services/model/liked_tenders_data/liked_tenders_data.dart @@ -0,0 +1,17 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:tm_app/data/services/model/liked_tender_feedback/liked_tender_feedback.dart'; +import '../meta/meta.dart'; + +part 'liked_tenders_data.freezed.dart'; +part 'liked_tenders_data.g.dart'; + +@freezed +abstract class LikedTendersData with _$LikedTendersData { + const factory LikedTendersData({ + required List? feedback, + required Meta? meta, + }) = _LikedTendersData; + + factory LikedTendersData.fromJson(Map json) => + _$LikedTendersDataFromJson(json); +} diff --git a/lib/data/services/model/liked_tenders_data/liked_tenders_data.freezed.dart b/lib/data/services/model/liked_tenders_data/liked_tenders_data.freezed.dart new file mode 100644 index 0000000..fde2103 --- /dev/null +++ b/lib/data/services/model/liked_tenders_data/liked_tenders_data.freezed.dart @@ -0,0 +1,312 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'liked_tenders_data.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$LikedTendersData { + + List? get feedback; Meta? get meta; +/// Create a copy of LikedTendersData +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$LikedTendersDataCopyWith get copyWith => _$LikedTendersDataCopyWithImpl(this as LikedTendersData, _$identity); + + /// Serializes this LikedTendersData to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is LikedTendersData&&const DeepCollectionEquality().equals(other.feedback, feedback)&&(identical(other.meta, meta) || other.meta == meta)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(feedback),meta); + +@override +String toString() { + return 'LikedTendersData(feedback: $feedback, meta: $meta)'; +} + + +} + +/// @nodoc +abstract mixin class $LikedTendersDataCopyWith<$Res> { + factory $LikedTendersDataCopyWith(LikedTendersData value, $Res Function(LikedTendersData) _then) = _$LikedTendersDataCopyWithImpl; +@useResult +$Res call({ + List? feedback, Meta? meta +}); + + +$MetaCopyWith<$Res>? get meta; + +} +/// @nodoc +class _$LikedTendersDataCopyWithImpl<$Res> + implements $LikedTendersDataCopyWith<$Res> { + _$LikedTendersDataCopyWithImpl(this._self, this._then); + + final LikedTendersData _self; + final $Res Function(LikedTendersData) _then; + +/// Create a copy of LikedTendersData +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? feedback = freezed,Object? meta = freezed,}) { + return _then(_self.copyWith( +feedback: freezed == feedback ? _self.feedback : feedback // ignore: cast_nullable_to_non_nullable +as List?,meta: freezed == meta ? _self.meta : meta // ignore: cast_nullable_to_non_nullable +as Meta?, + )); +} +/// Create a copy of LikedTendersData +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$MetaCopyWith<$Res>? get meta { + if (_self.meta == null) { + return null; + } + + return $MetaCopyWith<$Res>(_self.meta!, (value) { + return _then(_self.copyWith(meta: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [LikedTendersData]. +extension LikedTendersDataPatterns on LikedTendersData { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _LikedTendersData value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _LikedTendersData() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _LikedTendersData value) $default,){ +final _that = this; +switch (_that) { +case _LikedTendersData(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _LikedTendersData value)? $default,){ +final _that = this; +switch (_that) { +case _LikedTendersData() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( List? feedback, Meta? meta)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _LikedTendersData() when $default != null: +return $default(_that.feedback,_that.meta);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( List? feedback, Meta? meta) $default,) {final _that = this; +switch (_that) { +case _LikedTendersData(): +return $default(_that.feedback,_that.meta);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( List? feedback, Meta? meta)? $default,) {final _that = this; +switch (_that) { +case _LikedTendersData() when $default != null: +return $default(_that.feedback,_that.meta);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _LikedTendersData implements LikedTendersData { + const _LikedTendersData({required final List? feedback, required this.meta}): _feedback = feedback; + factory _LikedTendersData.fromJson(Map json) => _$LikedTendersDataFromJson(json); + + final List? _feedback; +@override List? get feedback { + final value = _feedback; + if (value == null) return null; + if (_feedback is EqualUnmodifiableListView) return _feedback; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); +} + +@override final Meta? meta; + +/// Create a copy of LikedTendersData +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$LikedTendersDataCopyWith<_LikedTendersData> get copyWith => __$LikedTendersDataCopyWithImpl<_LikedTendersData>(this, _$identity); + +@override +Map toJson() { + return _$LikedTendersDataToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _LikedTendersData&&const DeepCollectionEquality().equals(other._feedback, _feedback)&&(identical(other.meta, meta) || other.meta == meta)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_feedback),meta); + +@override +String toString() { + return 'LikedTendersData(feedback: $feedback, meta: $meta)'; +} + + +} + +/// @nodoc +abstract mixin class _$LikedTendersDataCopyWith<$Res> implements $LikedTendersDataCopyWith<$Res> { + factory _$LikedTendersDataCopyWith(_LikedTendersData value, $Res Function(_LikedTendersData) _then) = __$LikedTendersDataCopyWithImpl; +@override @useResult +$Res call({ + List? feedback, Meta? meta +}); + + +@override $MetaCopyWith<$Res>? get meta; + +} +/// @nodoc +class __$LikedTendersDataCopyWithImpl<$Res> + implements _$LikedTendersDataCopyWith<$Res> { + __$LikedTendersDataCopyWithImpl(this._self, this._then); + + final _LikedTendersData _self; + final $Res Function(_LikedTendersData) _then; + +/// Create a copy of LikedTendersData +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? feedback = freezed,Object? meta = freezed,}) { + return _then(_LikedTendersData( +feedback: freezed == feedback ? _self._feedback : feedback // ignore: cast_nullable_to_non_nullable +as List?,meta: freezed == meta ? _self.meta : meta // ignore: cast_nullable_to_non_nullable +as Meta?, + )); +} + +/// Create a copy of LikedTendersData +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$MetaCopyWith<$Res>? get meta { + if (_self.meta == null) { + return null; + } + + return $MetaCopyWith<$Res>(_self.meta!, (value) { + return _then(_self.copyWith(meta: value)); + }); +} +} + +// dart format on diff --git a/lib/data/services/model/liked_tenders_data/liked_tenders_data.g.dart b/lib/data/services/model/liked_tenders_data/liked_tenders_data.g.dart new file mode 100644 index 0000000..40052e9 --- /dev/null +++ b/lib/data/services/model/liked_tenders_data/liked_tenders_data.g.dart @@ -0,0 +1,24 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'liked_tenders_data.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_LikedTendersData _$LikedTendersDataFromJson(Map json) => + _LikedTendersData( + feedback: + (json['feedback'] as List?) + ?.map( + (e) => LikedTenderFeedback.fromJson(e as Map), + ) + .toList(), + meta: + json['meta'] == null + ? null + : Meta.fromJson(json['meta'] as Map), + ); + +Map _$LikedTendersDataToJson(_LikedTendersData instance) => + {'feedback': instance.feedback, 'meta': instance.meta}; diff --git a/lib/data/services/model/liked_tenders_response/liked_tenders_response.dart b/lib/data/services/model/liked_tenders_response/liked_tenders_response.dart new file mode 100644 index 0000000..86f5655 --- /dev/null +++ b/lib/data/services/model/liked_tenders_response/liked_tenders_response.dart @@ -0,0 +1,20 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:tm_app/data/services/model/liked_tenders_data/liked_tenders_data.dart'; + +import '../error/error_model.dart'; + +part 'liked_tenders_response.freezed.dart'; +part 'liked_tenders_response.g.dart'; + +@freezed +abstract class LikedTendersResponse with _$LikedTendersResponse { + const factory LikedTendersResponse({ + required bool? success, + required String? message, + required LikedTendersData? data, + required ErrorModel? error, + }) = _LikedTendersResponse; + + factory LikedTendersResponse.fromJson(Map json) => + _$LikedTendersResponseFromJson(json); +} diff --git a/lib/data/services/model/liked_tenders_response/liked_tenders_response.freezed.dart b/lib/data/services/model/liked_tenders_response/liked_tenders_response.freezed.dart new file mode 100644 index 0000000..d8a6bf9 --- /dev/null +++ b/lib/data/services/model/liked_tenders_response/liked_tenders_response.freezed.dart @@ -0,0 +1,334 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'liked_tenders_response.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; + +/// @nodoc +mixin _$LikedTendersResponse { + + bool? get success; String? get message; LikedTendersData? get data; ErrorModel? get error; +/// Create a copy of LikedTendersResponse +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$LikedTendersResponseCopyWith get copyWith => _$LikedTendersResponseCopyWithImpl(this as LikedTendersResponse, _$identity); + + /// Serializes this LikedTendersResponse to a JSON map. + Map toJson(); + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is LikedTendersResponse&&(identical(other.success, success) || other.success == success)&&(identical(other.message, message) || other.message == message)&&(identical(other.data, data) || other.data == data)&&(identical(other.error, error) || other.error == error)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,success,message,data,error); + +@override +String toString() { + return 'LikedTendersResponse(success: $success, message: $message, data: $data, error: $error)'; +} + + +} + +/// @nodoc +abstract mixin class $LikedTendersResponseCopyWith<$Res> { + factory $LikedTendersResponseCopyWith(LikedTendersResponse value, $Res Function(LikedTendersResponse) _then) = _$LikedTendersResponseCopyWithImpl; +@useResult +$Res call({ + bool? success, String? message, LikedTendersData? data, ErrorModel? error +}); + + +$LikedTendersDataCopyWith<$Res>? get data;$ErrorModelCopyWith<$Res>? get error; + +} +/// @nodoc +class _$LikedTendersResponseCopyWithImpl<$Res> + implements $LikedTendersResponseCopyWith<$Res> { + _$LikedTendersResponseCopyWithImpl(this._self, this._then); + + final LikedTendersResponse _self; + final $Res Function(LikedTendersResponse) _then; + +/// Create a copy of LikedTendersResponse +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? success = freezed,Object? message = freezed,Object? data = freezed,Object? error = freezed,}) { + return _then(_self.copyWith( +success: freezed == success ? _self.success : success // ignore: cast_nullable_to_non_nullable +as bool?,message: freezed == message ? _self.message : message // ignore: cast_nullable_to_non_nullable +as String?,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable +as LikedTendersData?,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable +as ErrorModel?, + )); +} +/// Create a copy of LikedTendersResponse +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$LikedTendersDataCopyWith<$Res>? get data { + if (_self.data == null) { + return null; + } + + return $LikedTendersDataCopyWith<$Res>(_self.data!, (value) { + return _then(_self.copyWith(data: value)); + }); +}/// Create a copy of LikedTendersResponse +/// 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; + } + + return $ErrorModelCopyWith<$Res>(_self.error!, (value) { + return _then(_self.copyWith(error: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [LikedTendersResponse]. +extension LikedTendersResponsePatterns on LikedTendersResponse { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _LikedTendersResponse value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _LikedTendersResponse() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _LikedTendersResponse value) $default,){ +final _that = this; +switch (_that) { +case _LikedTendersResponse(): +return $default(_that);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _LikedTendersResponse value)? $default,){ +final _that = this; +switch (_that) { +case _LikedTendersResponse() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( bool? success, String? message, LikedTendersData? data, ErrorModel? error)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _LikedTendersResponse() when $default != null: +return $default(_that.success,_that.message,_that.data,_that.error);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( bool? success, String? message, LikedTendersData? data, ErrorModel? error) $default,) {final _that = this; +switch (_that) { +case _LikedTendersResponse(): +return $default(_that.success,_that.message,_that.data,_that.error);case _: + throw StateError('Unexpected subclass'); + +} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( bool? success, String? message, LikedTendersData? data, ErrorModel? error)? $default,) {final _that = this; +switch (_that) { +case _LikedTendersResponse() when $default != null: +return $default(_that.success,_that.message,_that.data,_that.error);case _: + return null; + +} +} + +} + +/// @nodoc +@JsonSerializable() + +class _LikedTendersResponse implements LikedTendersResponse { + const _LikedTendersResponse({required this.success, required this.message, required this.data, required this.error}); + factory _LikedTendersResponse.fromJson(Map json) => _$LikedTendersResponseFromJson(json); + +@override final bool? success; +@override final String? message; +@override final LikedTendersData? data; +@override final ErrorModel? error; + +/// Create a copy of LikedTendersResponse +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$LikedTendersResponseCopyWith<_LikedTendersResponse> get copyWith => __$LikedTendersResponseCopyWithImpl<_LikedTendersResponse>(this, _$identity); + +@override +Map toJson() { + return _$LikedTendersResponseToJson(this, ); +} + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _LikedTendersResponse&&(identical(other.success, success) || other.success == success)&&(identical(other.message, message) || other.message == message)&&(identical(other.data, data) || other.data == data)&&(identical(other.error, error) || other.error == error)); +} + +@JsonKey(includeFromJson: false, includeToJson: false) +@override +int get hashCode => Object.hash(runtimeType,success,message,data,error); + +@override +String toString() { + return 'LikedTendersResponse(success: $success, message: $message, data: $data, error: $error)'; +} + + +} + +/// @nodoc +abstract mixin class _$LikedTendersResponseCopyWith<$Res> implements $LikedTendersResponseCopyWith<$Res> { + factory _$LikedTendersResponseCopyWith(_LikedTendersResponse value, $Res Function(_LikedTendersResponse) _then) = __$LikedTendersResponseCopyWithImpl; +@override @useResult +$Res call({ + bool? success, String? message, LikedTendersData? data, ErrorModel? error +}); + + +@override $LikedTendersDataCopyWith<$Res>? get data;@override $ErrorModelCopyWith<$Res>? get error; + +} +/// @nodoc +class __$LikedTendersResponseCopyWithImpl<$Res> + implements _$LikedTendersResponseCopyWith<$Res> { + __$LikedTendersResponseCopyWithImpl(this._self, this._then); + + final _LikedTendersResponse _self; + final $Res Function(_LikedTendersResponse) _then; + +/// Create a copy of LikedTendersResponse +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? success = freezed,Object? message = freezed,Object? data = freezed,Object? error = freezed,}) { + return _then(_LikedTendersResponse( +success: freezed == success ? _self.success : success // ignore: cast_nullable_to_non_nullable +as bool?,message: freezed == message ? _self.message : message // ignore: cast_nullable_to_non_nullable +as String?,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable +as LikedTendersData?,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable +as ErrorModel?, + )); +} + +/// Create a copy of LikedTendersResponse +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$LikedTendersDataCopyWith<$Res>? get data { + if (_self.data == null) { + return null; + } + + return $LikedTendersDataCopyWith<$Res>(_self.data!, (value) { + return _then(_self.copyWith(data: value)); + }); +}/// Create a copy of LikedTendersResponse +/// 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; + } + + return $ErrorModelCopyWith<$Res>(_self.error!, (value) { + return _then(_self.copyWith(error: value)); + }); +} +} + +// dart format on diff --git a/lib/data/services/model/liked_tenders_response/liked_tenders_response.g.dart b/lib/data/services/model/liked_tenders_response/liked_tenders_response.g.dart new file mode 100644 index 0000000..ba9fec7 --- /dev/null +++ b/lib/data/services/model/liked_tenders_response/liked_tenders_response.g.dart @@ -0,0 +1,31 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'liked_tenders_response.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_LikedTendersResponse _$LikedTendersResponseFromJson( + Map json, +) => _LikedTendersResponse( + success: json['success'] as bool?, + message: json['message'] as String?, + data: + json['data'] == null + ? null + : LikedTendersData.fromJson(json['data'] as Map), + error: + json['error'] == null + ? null + : ErrorModel.fromJson(json['error'] as Map), +); + +Map _$LikedTendersResponseToJson( + _LikedTendersResponse instance, +) => { + 'success': instance.success, + 'message': instance.message, + 'data': instance.data, + 'error': instance.error, +}; diff --git a/lib/gen/assets.gen.dart b/lib/gen/assets.gen.dart index bcb1129..327433a 100644 --- a/lib/gen/assets.gen.dart +++ b/lib/gen/assets.gen.dart @@ -117,6 +117,9 @@ class $AssetsIconsGen { /// File path: assets/icons/trailing_icon.svg String get trailingIcon => 'assets/icons/trailing_icon.svg'; + /// File path: assets/icons/trash.svg + String get trash => 'assets/icons/trash.svg'; + /// List of all assets List get values => [ se, @@ -153,6 +156,7 @@ class $AssetsIconsGen { tickCircle, tick, trailingIcon, + trash, ]; } diff --git a/lib/view_models/liked_tenders_view_model.dart b/lib/view_models/liked_tenders_view_model.dart new file mode 100644 index 0000000..931a1c0 --- /dev/null +++ b/lib/view_models/liked_tenders_view_model.dart @@ -0,0 +1,107 @@ +import 'package:flutter/material.dart'; +import 'package:tm_app/core/utils/result.dart'; +import 'package:tm_app/data/repositories/liked_tenders_repository.dart'; +import 'package:tm_app/data/services/model/liked_tender_feedback/liked_tender_feedback.dart'; +import 'package:tm_app/data/services/model/liked_tenders_response/liked_tenders_response.dart'; + +class LikedTendersViewModel with ChangeNotifier { + final LikedTendersRepository likedTendersRepository; + LikedTendersViewModel({required this.likedTendersRepository}) { + getLikedTenders(reset: true); + } + + bool isLoading = false; + bool isLoadingMore = false; + bool hasMore = true; + String? errorMessage; + LikedTendersResponse? data; + + int _offset = 0; + final int _limit = 20; + + Future getLikedTenders({bool reset = false}) async { + if (!reset && (isLoadingMore || !hasMore)) { + return; + } + + if (reset) { + _offset = 0; + hasMore = true; + } + + if (_offset == 0) { + isLoading = true; + } else { + isLoadingMore = true; + } + errorMessage = null; + notifyListeners(); + + final result = await likedTendersRepository.getLikedTenders( + offset: _offset, + limit: _limit, + ); + + switch (result) { + case Ok(): + final incoming = result.value; + final incomingList = incoming.data?.feedback ?? []; + + if (reset || data == null) { + data = incoming; + } else { + final existingList = data!.data!.feedback ?? []; + final combined = List.of(existingList) + ..addAll(incomingList); + + final newMeta = incoming.data?.meta ?? data!.data!.meta; + + data = data!.copyWith( + data: data!.data!.copyWith(feedback: combined, meta: newMeta), + ); + } + + // چون offset بر اساس limit حرکت می‌کنه + hasMore = incomingList.length == _limit; + _offset += _limit; + + errorMessage = null; + break; + + case Error(): + errorMessage = result.error.toString(); + break; + } + + isLoading = false; + isLoadingMore = false; + notifyListeners(); + } + + Future loadMoreIfNeeded() async { + if (isLoadingMore || !hasMore) { + return; + } + await getLikedTenders(reset: false); + } + + void removeTenderById(String id) { + final current = data; + final list = current?.data?.feedback; + if (current == null || list == null) { + return; + } + + final List updatedList = List.of( + list, + )..removeWhere((e) => e.id == id); + + final updatedMeta = current.data?.meta?.copyWith(total: updatedList.length); + + data = current.copyWith( + data: current.data?.copyWith(feedback: updatedList, meta: updatedMeta), + ); + + notifyListeners(); + } +} diff --git a/lib/views/home/pages/d_home_page.dart b/lib/views/home/pages/d_home_page.dart index 22f2022..409aa74 100644 --- a/lib/views/home/pages/d_home_page.dart +++ b/lib/views/home/pages/d_home_page.dart @@ -188,10 +188,12 @@ class DesktopHomePage extends StatelessWidget { title: HomeStrings.likedTenders, amount: homeViewModel.feedbackStats?.totalLikes.toString() ?? '0', textColor: AppColors.grey80, - enableTap: false, + enableTap: true, width: 178, height: 148, - onTap: () {}, + onTap: () { + LikedTendersRouteData().push(context); + }, ), ], ); diff --git a/lib/views/home/pages/m_home_page.dart b/lib/views/home/pages/m_home_page.dart index d88e39c..0e8bcc2 100644 --- a/lib/views/home/pages/m_home_page.dart +++ b/lib/views/home/pages/m_home_page.dart @@ -47,7 +47,7 @@ class MobileHomePage extends StatelessWidget { homeViewModel.tenderApprovalsStateResponse!, ), SizedBox(height: 8.0.h()), - _secondTenderCardsRow(homeViewModel), + _secondTenderCardsRow(homeViewModel, context), SizedBox(height: 40.0.h()), _yourTenderText(homeViewModel), _bottomListView(homeViewModel), @@ -126,8 +126,10 @@ class MobileHomePage extends StatelessWidget { ); } - // ignore: unused_element - Widget _secondTenderCardsRow(HomeViewModel homeViewModel) { + Widget _secondTenderCardsRow( + HomeViewModel homeViewModel, + BuildContext context, + ) { return Padding( padding: EdgeInsets.symmetric(horizontal: 24.0.w()), child: Row( @@ -148,8 +150,10 @@ class MobileHomePage extends StatelessWidget { title: HomeStrings.likedTenders, amount: homeViewModel.feedbackStats?.totalLikes.toString() ?? '0', textColor: AppColors.grey80, - enableTap: false, - onTap: () {}, + enableTap: true, + onTap: () { + LikedTendersRouteData().push(context); + }, ), ], ), diff --git a/lib/views/home/pages/t_home_page.dart b/lib/views/home/pages/t_home_page.dart index bf6f83b..a1e598c 100644 --- a/lib/views/home/pages/t_home_page.dart +++ b/lib/views/home/pages/t_home_page.dart @@ -81,7 +81,7 @@ class TabletHomePage extends StatelessWidget { homeViewModel.tenderApprovalsStateResponse!, ), SizedBox(height: 8.0.h()), - _secondTenderCardsRow(homeViewModel), + _secondTenderCardsRow(homeViewModel,context), SizedBox(height: 32.0.h()), _yourTenderText(homeViewModel), SizedBox(height: 8.0.h()), @@ -173,7 +173,10 @@ class TabletHomePage extends StatelessWidget { } // ignore: unused_element - Widget _secondTenderCardsRow(HomeViewModel homeViewModel) { + Widget _secondTenderCardsRow( + HomeViewModel homeViewModel, + BuildContext context, + ) { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -198,10 +201,12 @@ class TabletHomePage extends StatelessWidget { title: HomeStrings.likedTenders, amount: homeViewModel.feedbackStats?.totalLikes.toString() ?? '0', textColor: AppColors.grey80, - enableTap: false, + enableTap: true, width: double.infinity, height: 148, - onTap: () {}, + onTap: () { + LikedTendersRouteData().push(context); + }, ), ), ], diff --git a/lib/views/liked_tenders/pages/liked_tenders_desktop_page.dart b/lib/views/liked_tenders/pages/liked_tenders_desktop_page.dart new file mode 100644 index 0000000..7fe511c --- /dev/null +++ b/lib/views/liked_tenders/pages/liked_tenders_desktop_page.dart @@ -0,0 +1,169 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:provider/provider.dart'; +import 'package:tm_app/core/constants/assets.dart'; +import 'package:tm_app/core/theme/colors.dart'; +import 'package:tm_app/core/utils/size_config.dart'; +import 'package:tm_app/view_models/liked_tenders_view_model.dart'; +import 'package:tm_app/views/liked_tenders/widgets/liked_tenders_list_item.dart'; + +import '../../../core/routes/app_routes.dart'; +import '../../shared/desktop_navigation_widget.dart'; + +class LikedTendersDesktopPage extends StatefulWidget { + const LikedTendersDesktopPage({super.key}); + + @override + State createState() => + _LikedTendersDesktopPageState(); +} + +class _LikedTendersDesktopPageState extends State { + late ScrollController _scrollController; + + @override + void initState() { + super.initState(); + _scrollController = ScrollController()..addListener(_onScroll); + } + + void _onScroll() { + if (!_scrollController.hasClients) { + return; + } + final max = _scrollController.position.maxScrollExtent; + final pos = _scrollController.position.pixels; + const threshold = 200.0; + if (max - pos <= threshold) { + final vm = Provider.of(context, listen: false); + vm.loadMoreIfNeeded(); + } + } + + @override + void dispose() { + _scrollController.removeListener(_onScroll); + _scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.backgroundColor, + + body: Column( + children: [ + DesktopNavigationWidget( + currentIndex: 1, + onTabChanged: (index) { + switch (index) { + case 0: + Router.neglect(context, () => HomeRouteData().go(context)); + break; + case 1: + break; + case 2: + Router.neglect(context, () => ProfileRouteData().go(context)); + break; + } + }, + ), + Expanded( + child: SizedBox( + width: 740, + child: Column( + children: [ + Expanded( + child: Consumer( + builder: (context, viewModel, child) { + if (viewModel.isLoading && + (viewModel.data?.data?.feedback ?? []).isEmpty) { + return const Center( + child: CircularProgressIndicator( + color: AppColors.secondary50, + ), + ); + } + + if (viewModel.errorMessage != null) { + return Center(child: Text(viewModel.errorMessage!)); + } + + final feedback = viewModel.data?.data?.feedback ?? []; + + if (feedback.isEmpty) { + return const Center( + child: Text('چیزی برای نمایش نیست'), + ); + } + + final itemCount = + feedback.length + (viewModel.isLoadingMore ? 1 : 0); + + return RefreshIndicator( + onRefresh: () async { + await viewModel.getLikedTenders(reset: true); + }, + child: ListView.builder( + controller: _scrollController, + padding: EdgeInsets.symmetric( + horizontal: 24.0.w(), + vertical: 15.0.h(), + ), + itemCount: itemCount, + itemBuilder: (context, index) { + if (index < feedback.length) { + final item = feedback[index]; + return Padding( + padding: EdgeInsets.only(bottom: 20.0.h()), + child: Dismissible( + key: ValueKey(item.id ?? index), + direction: DismissDirection.endToStart, + background: Container( + alignment: Alignment.centerRight, + padding: EdgeInsets.symmetric( + horizontal: 20.0.w(), + ), + child: SvgPicture.asset( + AssetsManager.trash, + ), + ), + onDismissed: (_) { + if (item.id != null) { + viewModel.removeTenderById(item.id!); + } + }, + child: LikedListItem(tender: item.tender!), + ), + ); + } else { + return const Padding( + padding: EdgeInsets.symmetric(vertical: 20), + child: Center( + child: SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator( + color: AppColors.secondary50, + strokeWidth: 2, + ), + ), + ), + ); + } + }, + ), + ); + }, + ), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/views/liked_tenders/pages/liked_tenders_mobile_page.dart b/lib/views/liked_tenders/pages/liked_tenders_mobile_page.dart new file mode 100644 index 0000000..f072287 --- /dev/null +++ b/lib/views/liked_tenders/pages/liked_tenders_mobile_page.dart @@ -0,0 +1,136 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:provider/provider.dart'; +import 'package:tm_app/core/constants/assets.dart'; +import 'package:tm_app/core/theme/colors.dart'; +import 'package:tm_app/core/utils/size_config.dart'; +import 'package:tm_app/view_models/liked_tenders_view_model.dart'; +import 'package:tm_app/views/liked_tenders/strings/liked_tenders_strings.dart'; +import 'package:tm_app/views/liked_tenders/widgets/liked_tenders_list_item.dart'; +import 'package:tm_app/views/shared/tender_app_bar.dart'; + +class LikedTendersMobilePage extends StatefulWidget { + const LikedTendersMobilePage({super.key}); + + @override + State createState() => _LikedTendersMobilePageState(); +} + +class _LikedTendersMobilePageState extends State { + late final ScrollController _scrollController; + + @override + void initState() { + super.initState(); + _scrollController = ScrollController()..addListener(_onScroll); + } + + void _onScroll() { + if (!_scrollController.hasClients) { + return; + } + final max = _scrollController.position.maxScrollExtent; + final pos = _scrollController.position.pixels; + const threshold = 200.0; + if (max - pos <= threshold) { + final vm = Provider.of(context, listen: false); + vm.loadMoreIfNeeded(); + } + } + + @override + void dispose() { + _scrollController.removeListener(_onScroll); + _scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.backgroundColor, + appBar: appBar(context: context, title: LikedTendersStrings.likedTenders), + body: Column( + children: [ + Expanded( + child: Consumer( + builder: (context, viewModel, child) { + if (viewModel.isLoading && + (viewModel.data?.data?.feedback ?? []).isEmpty) { + return const Center( + child: CircularProgressIndicator( + color: AppColors.secondary50, + ), + ); + } + + if (viewModel.errorMessage != null) { + return Center(child: Text(viewModel.errorMessage!)); + } + + final feedback = viewModel.data?.data?.feedback ?? []; + + if (feedback.isEmpty) { + return const Center(child: Text('چیزی برای نمایش نیست')); + } + + final itemCount = feedback.length + (viewModel.isLoadingMore ? 1 : 0); + + return RefreshIndicator( + onRefresh: () async { + await viewModel.getLikedTenders(reset: true); + }, + child: ListView.builder( + controller: _scrollController, + padding: EdgeInsets.symmetric( + horizontal: 24.0.w(), + vertical: 15.0.h(), + ), + itemCount: itemCount, + itemBuilder: (context, index) { + if (index < feedback.length) { + final item = feedback[index]; + return Padding( + padding: EdgeInsets.only(bottom: 20.0.h()), + child: Dismissible( + key: ValueKey(item.id ?? index), + direction: DismissDirection.endToStart, + background: Container( + alignment: Alignment.centerRight, + padding: EdgeInsets.symmetric(horizontal: 20.0.w()), + child: SvgPicture.asset(AssetsManager.trash), + ), + onDismissed: (_) { + if (item.id != null) { + viewModel.removeTenderById(item.id!); + } + }, + child: LikedListItem(tender: item.tender!), + ), + ); + } else { + return const Padding( + padding: EdgeInsets.symmetric(vertical: 20), + child: Center( + child: SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator( + color: AppColors.secondary50, + strokeWidth: 2, + ), + ), + ), + ); + } + }, + ), + ); + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/views/liked_tenders/pages/liked_tenders_screen.dart b/lib/views/liked_tenders/pages/liked_tenders_screen.dart new file mode 100644 index 0000000..8f7a1c3 --- /dev/null +++ b/lib/views/liked_tenders/pages/liked_tenders_screen.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:tm_app/core/utils/size_config.dart'; +import 'package:tm_app/views/shared/responsive_builder.dart'; +import 'package:tm_app/views/liked_tenders/pages/liked_tenders_desktop_page.dart'; +import 'package:tm_app/views/liked_tenders/pages/liked_tenders_mobile_page.dart'; +import 'package:tm_app/views/liked_tenders/pages/liked_tenders_tablet_page.dart'; + +class LikedTendersScreen extends StatelessWidget { + const LikedTendersScreen({super.key}); + + @override + Widget build(BuildContext context) { + SizeConfig.init(context); + return ResponsiveBuilder( + mobile: LikedTendersMobilePage(), + tablet: LikedTendersTabletPage(), + desktop: LikedTendersDesktopPage(), + ); + } +} diff --git a/lib/views/liked_tenders/pages/liked_tenders_tablet_page.dart b/lib/views/liked_tenders/pages/liked_tenders_tablet_page.dart new file mode 100644 index 0000000..37cf573 --- /dev/null +++ b/lib/views/liked_tenders/pages/liked_tenders_tablet_page.dart @@ -0,0 +1,166 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:go_router/go_router.dart'; +import 'package:provider/provider.dart'; +import 'package:tm_app/core/constants/assets.dart'; +import 'package:tm_app/core/routes/app_routes.dart'; +import 'package:tm_app/core/theme/colors.dart'; +import 'package:tm_app/core/utils/size_config.dart'; +import 'package:tm_app/view_models/liked_tenders_view_model.dart'; +import 'package:tm_app/views/liked_tenders/strings/liked_tenders_strings.dart'; +import 'package:tm_app/views/liked_tenders/widgets/liked_tenders_list_item.dart'; + +import '../../shared/tablet_navigation_widget.dart'; +import '../../shared/tender_app_bar.dart'; + +class LikedTendersTabletPage extends StatefulWidget { + const LikedTendersTabletPage({super.key}); + + @override + State createState() => _LikedTendersTabletPageState(); +} + +class _LikedTendersTabletPageState extends State { + late final ScrollController _scrollController; + + @override + void initState() { + super.initState(); + _scrollController = ScrollController()..addListener(_onScroll); + } + + void _onScroll() { + if (!_scrollController.hasClients) { + return; + } + final max = _scrollController.position.maxScrollExtent; + final pos = _scrollController.position.pixels; + const threshold = 200.0; + if (max - pos <= threshold) { + final vm = Provider.of(context, listen: false); + vm.loadMoreIfNeeded(); + } + } + + @override + void dispose() { + _scrollController.removeListener(_onScroll); + _scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final GlobalKey key = GlobalKey(); + return Scaffold( + key: key, + backgroundColor: AppColors.backgroundColor, + appBar: tabletAppBar(title: LikedTendersStrings.likedTenders, key: key), + drawer: TabletNavigationWidget( + currentIndex: 1, + onTabChanged: (index) { + switch (index) { + case 0: + Router.neglect(context, () => HomeRouteData().go(context)); + break; + case 1: + break; + case 2: + context.pop(); + Router.neglect(context, () => ProfileRouteData().go(context)); + break; + } + }, + ), + body: Center( + child: SizedBox( + width: 720, + child: Column( + children: [ + Expanded( + child: Consumer( + builder: (context, viewModel, child) { + if (viewModel.isLoading && + (viewModel.data?.data?.feedback ?? []).isEmpty) { + return const Center( + child: CircularProgressIndicator( + color: AppColors.secondary50, + ), + ); + } + + if (viewModel.errorMessage != null) { + return Center(child: Text(viewModel.errorMessage!)); + } + + final feedback = viewModel.data?.data?.feedback ?? []; + + if (feedback.isEmpty) { + return const Center(child: Text('چیزی برای نمایش نیست')); + } + + final itemCount = + feedback.length + (viewModel.isLoadingMore ? 1 : 0); + return RefreshIndicator( + onRefresh: () async { + await viewModel.getLikedTenders(reset: true); + }, + child: ListView.builder( + controller: _scrollController, + padding: EdgeInsets.symmetric( + horizontal: 24.0.w(), + vertical: 15.0.h(), + ), + itemCount: itemCount, + itemBuilder: (context, index) { + if (index < feedback.length) { + final item = feedback[index]; + return Padding( + padding: EdgeInsets.only(bottom: 20.0.h()), + child: Dismissible( + key: ValueKey(item.id ?? index), + direction: DismissDirection.endToStart, + background: Container( + alignment: Alignment.centerRight, + padding: EdgeInsets.symmetric( + horizontal: 20.0.w(), + ), + child: SvgPicture.asset( + AssetsManager.trash, + ), + ), + onDismissed: (_) { + if (item.id != null) { + viewModel.removeTenderById(item.id!); + } + }, + child: LikedListItem(tender: item.tender!), + ), + ); + } else { + return const Padding( + padding: EdgeInsets.symmetric(vertical: 20), + child: Center( + child: SizedBox( + width: 24, + height: 24, + child: CircularProgressIndicator( + color: AppColors.secondary50, + strokeWidth: 2, + ), + ), + ), + ); + } + }, + ), + ); }, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/liked_tenders/strings/liked_tenders_strings.dart b/lib/views/liked_tenders/strings/liked_tenders_strings.dart new file mode 100644 index 0000000..80d5b0a --- /dev/null +++ b/lib/views/liked_tenders/strings/liked_tenders_strings.dart @@ -0,0 +1,15 @@ +class LikedTendersStrings { + LikedTendersStrings._(); + + static const likedTenders = 'Liked tenders'; + static const filter = 'Filter'; + static const selectDateRange = 'Select date range'; + static const status = 'Status'; + static const all = 'All'; + static const dislikeTenders = 'Dislike tenders'; + static const tendersSubmitted = 'Tenders submitted'; + static const confirm = 'Confirm'; + static const startDate = 'YYYY/MM/DD'; + static const endDate = 'YYYY/MM/DD'; + static const approvedTenders = 'Approved tenders'; +} diff --git a/lib/views/liked_tenders/widgets/liked_tenders_list_item.dart b/lib/views/liked_tenders/widgets/liked_tenders_list_item.dart new file mode 100644 index 0000000..d0006b2 --- /dev/null +++ b/lib/views/liked_tenders/widgets/liked_tenders_list_item.dart @@ -0,0 +1,148 @@ +import 'package:country_flags/country_flags.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:tm_app/core/constants/assets.dart'; +import 'package:tm_app/core/theme/colors.dart'; +import 'package:tm_app/core/utils/size_config.dart'; +import 'package:tm_app/data/services/model/tender_data/tender_data.dart'; +import 'package:tm_app/views/home/strings/home_strings.dart'; + +class LikedListItem extends StatelessWidget { + const LikedListItem({required this.tender, super.key}); + final TenderData tender; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: AppColors.cardBackground, + borderRadius: BorderRadius.circular(4), + border: Border.all(color: AppColors.grey30), + ), + margin: EdgeInsetsDirectional.only(end: 16.0.w()), + width: double.infinity, + height: 250.0.h(), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 16.0.w(), vertical: 16.0.h()), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 10.0.h()), + Container( + padding: EdgeInsets.symmetric( + horizontal: 10.0.w(), + vertical: 5.0.h(), + ), + decoration: BoxDecoration( + color: AppColors.primary30, + borderRadius: BorderRadius.circular(4), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '${HomeStrings.tenderDeadline} :', + style: TextStyle( + fontSize: 15.0.sp(), + fontWeight: FontWeight.w500, + color: AppColors.textBlue, + ), + ), + Text( + timeConvertor(tender.tenderDeadline!), + style: TextStyle( + fontSize: 14.0.sp(), + fontWeight: FontWeight.w500, + color: AppColors.grey80, + ), + ), + ], + ), + ), + SizedBox(height: 12.0.h()), + Text( + tender.title!, + style: TextStyle( + overflow: TextOverflow.ellipsis, + fontSize: 16.0.sp(), + fontWeight: FontWeight.w600, + color: AppColors.grey80, + ), + ), + SizedBox(height: 8.0.h()), + SizedBox( + width: double.infinity, + child: Text( + tender.description!, + textAlign: TextAlign.left, + maxLines: 4, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 14.0.sp(), + fontWeight: FontWeight.w400, + color: AppColors.grey70, + ), + ), + ), + Spacer(), + + Row( + children: [ + SvgPicture.asset(AssetsManager.location), + SizedBox(width: 1.0.w()), + Text( + tender.countryCode!, + style: TextStyle( + fontSize: 12.0.sp(), + fontWeight: FontWeight.w400, + color: AppColors.grey80, + ), + ), + SizedBox(width: 8.0.w()), + SizedBox( + width: 32.0.w(), + height: 21.0.h(), + child: CountryFlag.fromCountryCode( + tender.countryCode!, + shape: RoundedRectangle(4), + ), + ), + Spacer(), + Container( + width: 32.0.w(), + height: 32.0.w(), + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: AppColors.grey60, width: 1.5.w()), + ), + alignment: Alignment.center, + child: Icon(Icons.close, color: AppColors.grey60, size: 16), + ), + SizedBox(width: 30.0.w()), + Container( + width: 32.0.w(), + height: 32.0.w(), + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: AppColors.grey60, width: 1.5.w()), + ), + alignment: Alignment.center, + child: Icon(Icons.check, color: AppColors.grey60, size: 16), + ), + ], + ), + ], + ), + ), + ); + } +} + +String timeConvertor(int time) { + DateTime date = DateTime.fromMillisecondsSinceEpoch(time); + + String formattedDate = + "${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}"; + + return formattedDate; +} diff --git a/lib/views/your_tenders/pages/your_tenders_tablet_page.dart b/lib/views/your_tenders/pages/your_tenders_tablet_page.dart index 29f16be..3f9d4e6 100644 --- a/lib/views/your_tenders/pages/your_tenders_tablet_page.dart +++ b/lib/views/your_tenders/pages/your_tenders_tablet_page.dart @@ -47,7 +47,7 @@ class _YourTendersTabletPageState extends State key: key, backgroundColor: AppColors.backgroundColor, appBar: tabletAppBar( - title: YourTendersStrings.yourTendersTitle, + title: YourTendersStrings.yourTenders, key: key, ), drawer: TabletNavigationWidget( diff --git a/lib/views/your_tenders/strings/your_tenders_strings.dart b/lib/views/your_tenders/strings/your_tenders_strings.dart index 4be8110..d8929b5 100644 --- a/lib/views/your_tenders/strings/your_tenders_strings.dart +++ b/lib/views/your_tenders/strings/your_tenders_strings.dart @@ -2,7 +2,6 @@ class YourTendersStrings { YourTendersStrings._(); static const String yourTenders = 'Your tenders'; - static const String yourTendersTitle = 'Your tenders'; static const filter = 'Filter'; static const selectDateRange = 'Select date range'; static const status = 'Status'; diff --git a/lib/views/your_tenders/widgets/tablet_desktop_appbar.dart b/lib/views/your_tenders/widgets/tablet_desktop_appbar.dart index 66b18c2..9b18ee0 100644 --- a/lib/views/your_tenders/widgets/tablet_desktop_appbar.dart +++ b/lib/views/your_tenders/widgets/tablet_desktop_appbar.dart @@ -20,7 +20,7 @@ class TabletDesktopAppbar extends StatelessWidget { ), SizedBox(width: 8.0.w()), Text( - YourTendersStrings.yourTendersTitle, + YourTendersStrings.yourTenders, style: TextStyle( fontSize: 20, fontWeight: FontWeight.w600, diff --git a/pubspec.lock b/pubspec.lock index 0c8e778..a2774c6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.13.0" + version: "2.12.0" boolean_selector: dependency: transitive description: @@ -229,10 +229,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" url: "https://pub.dev" source: hosted - version: "1.3.3" + version: "1.3.2" ffi: dependency: transitive description: @@ -468,10 +468,10 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "10.0.8" leak_tracker_flutter_testing: dependency: transitive description: @@ -873,10 +873,10 @@ packages: dependency: transitive description: name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" url: "https://pub.dev" source: hosted - version: "15.0.0" + version: "14.3.1" watcher: dependency: transitive description: