Add Docker support and enhance data models for tender details

- Introduced a .dockerignore file to optimize Docker builds by excluding unnecessary files.
- Updated the Dockerfile to use a Flutter base image, enabling web builds for the application.
- Added new data models for `Lot` and `OrganizationAddress` to represent tender details more accurately.
- Enhanced the `Organization` model to include `companyId` and a nested `address` field.
- Updated the `TenderData` model to include new fields such as `noticeTypeCode`, `formType`, and `lots`, reflecting the API response structure.
- Regenerated necessary code for data models to ensure compatibility with the updated structures.
This commit is contained in:
AmirReza Jamali
2026-06-03 13:32:42 +03:30
parent 83b77c05ef
commit dde66521f6
35 changed files with 1362 additions and 659 deletions
+16
View File
@@ -0,0 +1,16 @@
build
.dart_tool
.git
.gitignore
.drone.yml
.vscode
.idea
.flutter-plugins-dependencies
README.md
*.md
Dockerfile
Dockerfile-old
.dockerignore
artifacts
ios
android
+22 -2
View File
@@ -1,3 +1,23 @@
FROM docker-mirror.ravanertebat.ir/hub/nginx:1.25.2-alpine3.18-slim
FROM docker-mirror.ravanertebat.ir/ghcr/cirruslabs/flutter:3.38.10 AS base
COPY ./artifacts/flutter/tm_pwa /usr/share/nginx/html
WORKDIR /app
RUN flutter config --enable-web
FROM base AS deps
COPY pubspec.yaml pubspec.lock ./
RUN flutter pub get
FROM base AS builder
# Pull in the resolved package config from deps so `flutter pub get` is not
# re-run when only source files change.
COPY --from=deps /app/.dart_tool ./.dart_tool
COPY . .
RUN flutter build web --release --output=build/tm_pwa
FROM docker-mirror.ravanertebat.ir/hub/nginx:1.25.2-alpine3.18-slim AS runner
COPY --from=builder /app/build/tm_pwa /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
+1 -19
View File
@@ -47,7 +47,6 @@ mixin _$LoginScreenRoute on GoRouteData {
RouteBase get $splashScreenRoute => GoRouteData.$route(
path: '/splash',
factory: _$SplashScreenRoute._fromState,
);
@@ -111,40 +110,33 @@ RouteBase get $appShellRouteData => StatefulShellRouteData.$route(
branches: [
StatefulShellBranchData.$branch(
navigatorKey: HomeBranch.$navigatorKey,
routes: [
GoRouteData.$route(path: '/home', factory: _$HomeRouteData._fromState),
],
),
StatefulShellBranchData.$branch(
navigatorKey: TendersBranch.$navigatorKey,
routes: [
GoRouteData.$route(
path: '/tenders',
factory: _$TendersRouteData._fromState,
),
],
),
StatefulShellBranchData.$branch(
navigatorKey: NotificationBranch.$navigatorKey,
routes: [
GoRouteData.$route(
path: '/notification',
factory: _$NotificationRouteData._fromState,
),
],
),
StatefulShellBranchData.$branch(
navigatorKey: ProfileBranch.$navigatorKey,
routes: [
GoRouteData.$route(
path: '/profile',
factory: _$ProfileRouteData._fromState,
),
],
@@ -242,7 +234,6 @@ mixin _$ProfileRouteData on GoRouteData {
RouteBase get $yourTendersRouteData => GoRouteData.$route(
path: '/your-tenders',
factory: _$YourTendersRouteData._fromState,
);
@@ -274,7 +265,6 @@ mixin _$YourTendersRouteData on GoRouteData {
RouteBase get $finalCompletionOfDocumentsRouteData => GoRouteData.$route(
path: '/final_completion_of_documents',
factory: _$FinalCompletionOfDocumentsRouteData._fromState,
);
@@ -302,7 +292,6 @@ mixin _$FinalCompletionOfDocumentsRouteData on GoRouteData {
RouteBase get $completionOfDocumentsRouteData => GoRouteData.$route(
path: '/completion_of_documents',
factory: _$CompletionOfDocumentsRouteData._fromState,
);
@@ -318,9 +307,7 @@ mixin _$CompletionOfDocumentsRouteData on GoRouteData {
@override
String get location => GoRouteData.$location(
'/completion_of_documents',
queryParams: {
if (_self.tenderId != null) 'tender-id': _self.tenderId!,
},
queryParams: {if (_self.tenderId != null) 'tender-id': _self.tenderId},
);
@override
@@ -339,7 +326,6 @@ mixin _$CompletionOfDocumentsRouteData on GoRouteData {
RouteBase get $likedTendersRouteData => GoRouteData.$route(
path: '/like-tenders',
factory: _$LikedTendersRouteData._fromState,
);
@@ -390,7 +376,6 @@ mixin _$BoardRouteData on GoRouteData {
RouteBase get $tenderDetailRouteData => GoRouteData.$route(
path: '/tender-detail',
factory: _$TenderDetailRouteData._fromState,
);
@@ -422,7 +407,6 @@ mixin _$TenderDetailRouteData on GoRouteData {
RouteBase get $forgotPasswordRouteData => GoRouteData.$route(
path: '/forgot-password',
factory: _$ForgotPasswordRouteData._fromState,
);
@@ -449,7 +433,6 @@ mixin _$ForgotPasswordRouteData on GoRouteData {
RouteBase get $forgotPasswordOtpRouteData => GoRouteData.$route(
path: '/forgot-password-otp',
factory: _$ForgotPasswordOtpRouteData._fromState,
);
@@ -481,7 +464,6 @@ mixin _$ForgotPasswordOtpRouteData on GoRouteData {
RouteBase get $forgotPasswordCreateRouteData => GoRouteData.$route(
path: '/forgot-password-create',
factory: _$ForgotPasswordCreateRouteData._fromState,
);
+20
View File
@@ -0,0 +1,20 @@
// ignore_for_file: invalid_annotation_target
import 'package:freezed_annotation/freezed_annotation.dart';
part 'lot.freezed.dart';
part 'lot.g.dart';
// AI-NOTE (for PR reviewer): New model for entries in the tender-details
// `lots` array. Only lot_id, title and description are mapped — the fields the
// tender-details "Lots" section renders. Add more fields here if the UI grows.
@freezed
abstract class Lot with _$Lot {
const factory Lot({
@JsonKey(name: 'lot_id') required String? lotId,
required String? title,
required String? description,
}) = _Lot;
factory Lot.fromJson(Map<String, Object?> json) => _$LotFromJson(json);
}
@@ -0,0 +1,283 @@
// 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 'lot.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$Lot {
@JsonKey(name: 'lot_id') String? get lotId; String? get title; String? get description;
/// Create a copy of Lot
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$LotCopyWith<Lot> get copyWith => _$LotCopyWithImpl<Lot>(this as Lot, _$identity);
/// Serializes this Lot to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is Lot&&(identical(other.lotId, lotId) || other.lotId == lotId)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,lotId,title,description);
@override
String toString() {
return 'Lot(lotId: $lotId, title: $title, description: $description)';
}
}
/// @nodoc
abstract mixin class $LotCopyWith<$Res> {
factory $LotCopyWith(Lot value, $Res Function(Lot) _then) = _$LotCopyWithImpl;
@useResult
$Res call({
@JsonKey(name: 'lot_id') String? lotId, String? title, String? description
});
}
/// @nodoc
class _$LotCopyWithImpl<$Res>
implements $LotCopyWith<$Res> {
_$LotCopyWithImpl(this._self, this._then);
final Lot _self;
final $Res Function(Lot) _then;
/// Create a copy of Lot
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? lotId = freezed,Object? title = freezed,Object? description = freezed,}) {
return _then(_self.copyWith(
lotId: freezed == lotId ? _self.lotId : lotId // ignore: cast_nullable_to_non_nullable
as String?,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// Adds pattern-matching-related methods to [Lot].
extension LotPatterns on Lot {
/// 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 extends Object?>(TResult Function( _Lot value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _Lot() 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 extends Object?>(TResult Function( _Lot value) $default,){
final _that = this;
switch (_that) {
case _Lot():
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 extends Object?>(TResult? Function( _Lot value)? $default,){
final _that = this;
switch (_that) {
case _Lot() 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 extends Object?>(TResult Function(@JsonKey(name: 'lot_id') String? lotId, String? title, String? description)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _Lot() when $default != null:
return $default(_that.lotId,_that.title,_that.description);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 extends Object?>(TResult Function(@JsonKey(name: 'lot_id') String? lotId, String? title, String? description) $default,) {final _that = this;
switch (_that) {
case _Lot():
return $default(_that.lotId,_that.title,_that.description);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 extends Object?>(TResult? Function(@JsonKey(name: 'lot_id') String? lotId, String? title, String? description)? $default,) {final _that = this;
switch (_that) {
case _Lot() when $default != null:
return $default(_that.lotId,_that.title,_that.description);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _Lot implements Lot {
const _Lot({@JsonKey(name: 'lot_id') required this.lotId, required this.title, required this.description});
factory _Lot.fromJson(Map<String, dynamic> json) => _$LotFromJson(json);
@override@JsonKey(name: 'lot_id') final String? lotId;
@override final String? title;
@override final String? description;
/// Create a copy of Lot
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$LotCopyWith<_Lot> get copyWith => __$LotCopyWithImpl<_Lot>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$LotToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Lot&&(identical(other.lotId, lotId) || other.lotId == lotId)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,lotId,title,description);
@override
String toString() {
return 'Lot(lotId: $lotId, title: $title, description: $description)';
}
}
/// @nodoc
abstract mixin class _$LotCopyWith<$Res> implements $LotCopyWith<$Res> {
factory _$LotCopyWith(_Lot value, $Res Function(_Lot) _then) = __$LotCopyWithImpl;
@override @useResult
$Res call({
@JsonKey(name: 'lot_id') String? lotId, String? title, String? description
});
}
/// @nodoc
class __$LotCopyWithImpl<$Res>
implements _$LotCopyWith<$Res> {
__$LotCopyWithImpl(this._self, this._then);
final _Lot _self;
final $Res Function(_Lot) _then;
/// Create a copy of Lot
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? lotId = freezed,Object? title = freezed,Object? description = freezed,}) {
return _then(_Lot(
lotId: freezed == lotId ? _self.lotId : lotId // ignore: cast_nullable_to_non_nullable
as String?,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
// dart format on
+19
View File
@@ -0,0 +1,19 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'lot.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_Lot _$LotFromJson(Map<String, dynamic> json) => _Lot(
lotId: json['lot_id'] as String?,
title: json['title'] as String?,
description: json['description'] as String?,
);
Map<String, dynamic> _$LotToJson(_Lot instance) => <String, dynamic>{
'lot_id': instance.lotId,
'title': instance.title,
'description': instance.description,
};
@@ -1,15 +1,22 @@
// ignore_for_file: invalid_annotation_target
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:tm_app/data/services/model/organization/organization_address.dart';
part 'organization.freezed.dart';
part 'organization.g.dart';
// AI-NOTE (for PR reviewer): Added company_id and a nested address
// (OrganizationAddress) to surface buyer details on the tender-details page.
// A dedicated OrganizationAddress type is used instead of the existing
// `Address` model because the buyer address shape differs (city_name +
// country_code only) and is unrelated to the user-profile Address.
@freezed
abstract class Organization with _$Organization {
const factory Organization({
required String? name,
@JsonKey(name: 'company_id') required String? companyId,
required OrganizationAddress? address,
}) = _Organization;
factory Organization.fromJson(Map<String, Object?> json) =>
@@ -15,7 +15,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$Organization {
String? get name;
String? get name;@JsonKey(name: 'company_id') String? get companyId; OrganizationAddress? get address;
/// Create a copy of Organization
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -28,16 +28,16 @@ $OrganizationCopyWith<Organization> get copyWith => _$OrganizationCopyWithImpl<O
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is Organization&&(identical(other.name, name) || other.name == name));
return identical(this, other) || (other.runtimeType == runtimeType&&other is Organization&&(identical(other.name, name) || other.name == name)&&(identical(other.companyId, companyId) || other.companyId == companyId)&&(identical(other.address, address) || other.address == address));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,name);
int get hashCode => Object.hash(runtimeType,name,companyId,address);
@override
String toString() {
return 'Organization(name: $name)';
return 'Organization(name: $name, companyId: $companyId, address: $address)';
}
@@ -48,11 +48,11 @@ abstract mixin class $OrganizationCopyWith<$Res> {
factory $OrganizationCopyWith(Organization value, $Res Function(Organization) _then) = _$OrganizationCopyWithImpl;
@useResult
$Res call({
String? name
String? name,@JsonKey(name: 'company_id') String? companyId, OrganizationAddress? address
});
$OrganizationAddressCopyWith<$Res>? get address;
}
/// @nodoc
@@ -65,13 +65,27 @@ class _$OrganizationCopyWithImpl<$Res>
/// Create a copy of Organization
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? name = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? name = freezed,Object? companyId = freezed,Object? address = freezed,}) {
return _then(_self.copyWith(
name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String?,
as String?,companyId: freezed == companyId ? _self.companyId : companyId // ignore: cast_nullable_to_non_nullable
as String?,address: freezed == address ? _self.address : address // ignore: cast_nullable_to_non_nullable
as OrganizationAddress?,
));
}
/// Create a copy of Organization
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$OrganizationAddressCopyWith<$Res>? get address {
if (_self.address == null) {
return null;
}
return $OrganizationAddressCopyWith<$Res>(_self.address!, (value) {
return _then(_self.copyWith(address: value));
});
}
}
@@ -153,10 +167,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String? name)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String? name, @JsonKey(name: 'company_id') String? companyId, OrganizationAddress? address)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _Organization() when $default != null:
return $default(_that.name);case _:
return $default(_that.name,_that.companyId,_that.address);case _:
return orElse();
}
@@ -174,10 +188,10 @@ return $default(_that.name);case _:
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String? name) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String? name, @JsonKey(name: 'company_id') String? companyId, OrganizationAddress? address) $default,) {final _that = this;
switch (_that) {
case _Organization():
return $default(_that.name);case _:
return $default(_that.name,_that.companyId,_that.address);case _:
throw StateError('Unexpected subclass');
}
@@ -194,10 +208,10 @@ return $default(_that.name);case _:
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String? name)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String? name, @JsonKey(name: 'company_id') String? companyId, OrganizationAddress? address)? $default,) {final _that = this;
switch (_that) {
case _Organization() when $default != null:
return $default(_that.name);case _:
return $default(_that.name,_that.companyId,_that.address);case _:
return null;
}
@@ -209,10 +223,12 @@ return $default(_that.name);case _:
@JsonSerializable()
class _Organization implements Organization {
const _Organization({required this.name});
const _Organization({required this.name, @JsonKey(name: 'company_id') required this.companyId, required this.address});
factory _Organization.fromJson(Map<String, dynamic> json) => _$OrganizationFromJson(json);
@override final String? name;
@override@JsonKey(name: 'company_id') final String? companyId;
@override final OrganizationAddress? address;
/// Create a copy of Organization
/// with the given fields replaced by the non-null parameter values.
@@ -227,16 +243,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Organization&&(identical(other.name, name) || other.name == name));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Organization&&(identical(other.name, name) || other.name == name)&&(identical(other.companyId, companyId) || other.companyId == companyId)&&(identical(other.address, address) || other.address == address));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,name);
int get hashCode => Object.hash(runtimeType,name,companyId,address);
@override
String toString() {
return 'Organization(name: $name)';
return 'Organization(name: $name, companyId: $companyId, address: $address)';
}
@@ -247,11 +263,11 @@ abstract mixin class _$OrganizationCopyWith<$Res> implements $OrganizationCopyWi
factory _$OrganizationCopyWith(_Organization value, $Res Function(_Organization) _then) = __$OrganizationCopyWithImpl;
@override @useResult
$Res call({
String? name
String? name,@JsonKey(name: 'company_id') String? companyId, OrganizationAddress? address
});
@override $OrganizationAddressCopyWith<$Res>? get address;
}
/// @nodoc
@@ -264,14 +280,28 @@ class __$OrganizationCopyWithImpl<$Res>
/// Create a copy of Organization
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? name = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? name = freezed,Object? companyId = freezed,Object? address = freezed,}) {
return _then(_Organization(
name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String?,
as String?,companyId: freezed == companyId ? _self.companyId : companyId // ignore: cast_nullable_to_non_nullable
as String?,address: freezed == address ? _self.address : address // ignore: cast_nullable_to_non_nullable
as OrganizationAddress?,
));
}
/// Create a copy of Organization
/// with the given fields replaced by the non-null parameter values.
@override
@pragma('vm:prefer-inline')
$OrganizationAddressCopyWith<$Res>? get address {
if (_self.address == null) {
return null;
}
return $OrganizationAddressCopyWith<$Res>(_self.address!, (value) {
return _then(_self.copyWith(address: value));
});
}
}
// dart format on
@@ -7,7 +7,20 @@ part of 'organization.dart';
// **************************************************************************
_Organization _$OrganizationFromJson(Map<String, dynamic> json) =>
_Organization(name: json['name'] as String?);
_Organization(
name: json['name'] as String?,
companyId: json['company_id'] as String?,
address:
json['address'] == null
? null
: OrganizationAddress.fromJson(
json['address'] as Map<String, dynamic>,
),
);
Map<String, dynamic> _$OrganizationToJson(_Organization instance) =>
<String, dynamic>{'name': instance.name};
<String, dynamic>{
'name': instance.name,
'company_id': instance.companyId,
'address': instance.address,
};
@@ -0,0 +1,21 @@
// ignore_for_file: invalid_annotation_target
import 'package:freezed_annotation/freezed_annotation.dart';
part 'organization_address.freezed.dart';
part 'organization_address.g.dart';
// AI-NOTE (for PR reviewer): New model for the buyer_organization.address
// object from the tender-details API. Kept separate from the existing profile
// `Address` model because the shapes differ (this one only needs city_name and
// country_code). Only the fields the UI shows are mapped.
@freezed
abstract class OrganizationAddress with _$OrganizationAddress {
const factory OrganizationAddress({
@JsonKey(name: 'city_name') required String? cityName,
@JsonKey(name: 'country_code') required String? countryCode,
}) = _OrganizationAddress;
factory OrganizationAddress.fromJson(Map<String, Object?> json) =>
_$OrganizationAddressFromJson(json);
}
@@ -0,0 +1,280 @@
// 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 'organization_address.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$OrganizationAddress {
@JsonKey(name: 'city_name') String? get cityName;@JsonKey(name: 'country_code') String? get countryCode;
/// Create a copy of OrganizationAddress
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
$OrganizationAddressCopyWith<OrganizationAddress> get copyWith => _$OrganizationAddressCopyWithImpl<OrganizationAddress>(this as OrganizationAddress, _$identity);
/// Serializes this OrganizationAddress to a JSON map.
Map<String, dynamic> toJson();
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is OrganizationAddress&&(identical(other.cityName, cityName) || other.cityName == cityName)&&(identical(other.countryCode, countryCode) || other.countryCode == countryCode));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,cityName,countryCode);
@override
String toString() {
return 'OrganizationAddress(cityName: $cityName, countryCode: $countryCode)';
}
}
/// @nodoc
abstract mixin class $OrganizationAddressCopyWith<$Res> {
factory $OrganizationAddressCopyWith(OrganizationAddress value, $Res Function(OrganizationAddress) _then) = _$OrganizationAddressCopyWithImpl;
@useResult
$Res call({
@JsonKey(name: 'city_name') String? cityName,@JsonKey(name: 'country_code') String? countryCode
});
}
/// @nodoc
class _$OrganizationAddressCopyWithImpl<$Res>
implements $OrganizationAddressCopyWith<$Res> {
_$OrganizationAddressCopyWithImpl(this._self, this._then);
final OrganizationAddress _self;
final $Res Function(OrganizationAddress) _then;
/// Create a copy of OrganizationAddress
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? cityName = freezed,Object? countryCode = freezed,}) {
return _then(_self.copyWith(
cityName: freezed == cityName ? _self.cityName : cityName // ignore: cast_nullable_to_non_nullable
as String?,countryCode: freezed == countryCode ? _self.countryCode : countryCode // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// Adds pattern-matching-related methods to [OrganizationAddress].
extension OrganizationAddressPatterns on OrganizationAddress {
/// 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 extends Object?>(TResult Function( _OrganizationAddress value)? $default,{required TResult orElse(),}){
final _that = this;
switch (_that) {
case _OrganizationAddress() 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 extends Object?>(TResult Function( _OrganizationAddress value) $default,){
final _that = this;
switch (_that) {
case _OrganizationAddress():
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 extends Object?>(TResult? Function( _OrganizationAddress value)? $default,){
final _that = this;
switch (_that) {
case _OrganizationAddress() 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 extends Object?>(TResult Function(@JsonKey(name: 'city_name') String? cityName, @JsonKey(name: 'country_code') String? countryCode)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _OrganizationAddress() when $default != null:
return $default(_that.cityName,_that.countryCode);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 extends Object?>(TResult Function(@JsonKey(name: 'city_name') String? cityName, @JsonKey(name: 'country_code') String? countryCode) $default,) {final _that = this;
switch (_that) {
case _OrganizationAddress():
return $default(_that.cityName,_that.countryCode);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 extends Object?>(TResult? Function(@JsonKey(name: 'city_name') String? cityName, @JsonKey(name: 'country_code') String? countryCode)? $default,) {final _that = this;
switch (_that) {
case _OrganizationAddress() when $default != null:
return $default(_that.cityName,_that.countryCode);case _:
return null;
}
}
}
/// @nodoc
@JsonSerializable()
class _OrganizationAddress implements OrganizationAddress {
const _OrganizationAddress({@JsonKey(name: 'city_name') required this.cityName, @JsonKey(name: 'country_code') required this.countryCode});
factory _OrganizationAddress.fromJson(Map<String, dynamic> json) => _$OrganizationAddressFromJson(json);
@override@JsonKey(name: 'city_name') final String? cityName;
@override@JsonKey(name: 'country_code') final String? countryCode;
/// Create a copy of OrganizationAddress
/// with the given fields replaced by the non-null parameter values.
@override @JsonKey(includeFromJson: false, includeToJson: false)
@pragma('vm:prefer-inline')
_$OrganizationAddressCopyWith<_OrganizationAddress> get copyWith => __$OrganizationAddressCopyWithImpl<_OrganizationAddress>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$OrganizationAddressToJson(this, );
}
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _OrganizationAddress&&(identical(other.cityName, cityName) || other.cityName == cityName)&&(identical(other.countryCode, countryCode) || other.countryCode == countryCode));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,cityName,countryCode);
@override
String toString() {
return 'OrganizationAddress(cityName: $cityName, countryCode: $countryCode)';
}
}
/// @nodoc
abstract mixin class _$OrganizationAddressCopyWith<$Res> implements $OrganizationAddressCopyWith<$Res> {
factory _$OrganizationAddressCopyWith(_OrganizationAddress value, $Res Function(_OrganizationAddress) _then) = __$OrganizationAddressCopyWithImpl;
@override @useResult
$Res call({
@JsonKey(name: 'city_name') String? cityName,@JsonKey(name: 'country_code') String? countryCode
});
}
/// @nodoc
class __$OrganizationAddressCopyWithImpl<$Res>
implements _$OrganizationAddressCopyWith<$Res> {
__$OrganizationAddressCopyWithImpl(this._self, this._then);
final _OrganizationAddress _self;
final $Res Function(_OrganizationAddress) _then;
/// Create a copy of OrganizationAddress
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? cityName = freezed,Object? countryCode = freezed,}) {
return _then(_OrganizationAddress(
cityName: freezed == cityName ? _self.cityName : cityName // ignore: cast_nullable_to_non_nullable
as String?,countryCode: freezed == countryCode ? _self.countryCode : countryCode // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
// dart format on
@@ -0,0 +1,20 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'organization_address.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_OrganizationAddress _$OrganizationAddressFromJson(Map<String, dynamic> json) =>
_OrganizationAddress(
cityName: json['city_name'] as String?,
countryCode: json['country_code'] as String?,
);
Map<String, dynamic> _$OrganizationAddressToJson(
_OrganizationAddress instance,
) => <String, dynamic>{
'city_name': instance.cityName,
'country_code': instance.countryCode,
};
@@ -2,11 +2,19 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:tm_app/core/utils/date_utils.dart';
import 'package:tm_app/data/services/model/lot/lot.dart';
import 'package:tm_app/data/services/model/organization/organization.dart';
part 'tender_data.freezed.dart';
part 'tender_data.g.dart';
// AI-NOTE (for PR reviewer): New fields were added to mirror the
// /tenders/details/:id response so the tender-details page can show them:
// notice_type_code, form_type, notice_language_code, issue_date, issue_time,
// lots, official_languages. issue_date/issue_time reuse unixTimestampFromJson
// (epoch seconds, tolerant of ms/string) like the other timestamp fields. All
// fields are nullable to stay resilient to partial API payloads. Freezed/json
// codegen (.freezed.dart/.g.dart) was regenerated for these.
@freezed
abstract class TenderData with _$TenderData {
const factory TenderData({
@@ -16,6 +24,13 @@ abstract class TenderData with _$TenderData {
@JsonKey(name: 'tender_id') required String? tenderId,
@JsonKey(name: 'notice_publication_id')
required String? noticePublicationId,
@JsonKey(name: 'notice_type_code') required String? noticeTypeCode,
@JsonKey(name: 'form_type') required String? formType,
@JsonKey(name: 'notice_language_code') required String? noticeLanguageCode,
@JsonKey(name: 'issue_date', fromJson: unixTimestampFromJson)
required int? issueDate,
@JsonKey(name: 'issue_time', fromJson: unixTimestampFromJson)
required int? issueTime,
@JsonKey(name: 'publication_date', fromJson: unixTimestampFromJson)
required int? publicationDate,
required String? title,
@@ -37,6 +52,9 @@ abstract class TenderData with _$TenderData {
@JsonKey(name: 'duration_unit') required String? durationUnit,
@JsonKey(name: 'buyer_organization')
required Organization? buyerOrganization,
required List<Lot>? lots,
@JsonKey(name: 'official_languages')
required List<String>? officialLanguages,
required String? status,
}) = _TenderData;
@@ -15,7 +15,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$TenderData {
bool? get success; String? get message; String? get id;@JsonKey(name: 'tender_id') String? get tenderId;@JsonKey(name: 'notice_publication_id') String? get noticePublicationId;@JsonKey(name: 'publication_date') int? get publicationDate; String? get title; String? get description;@JsonKey(name: 'procurement_type_code') String? get procurementTypeCode;@JsonKey(name: 'procedure_code') String? get procedureCode;@JsonKey(name: 'estimated_value') int? get estimatedValue; String? get currency; String? get duration;@JsonKey(name: 'tender_deadline') int? get tenderDeadline;@JsonKey(name: 'submission_deadline') int? get submissionDeadline;@JsonKey(name: 'application_deadline') int? get applicationDeadline;@JsonKey(name: 'submission_url') String? get submissionUrl;@JsonKey(name: 'country_code') String? get countryCode;@JsonKey(name: 'duration_unit') String? get durationUnit;@JsonKey(name: 'buyer_organization') Organization? get buyerOrganization; String? get status;
bool? get success; String? get message; String? get id;@JsonKey(name: 'tender_id') String? get tenderId;@JsonKey(name: 'notice_publication_id') String? get noticePublicationId;@JsonKey(name: 'notice_type_code') String? get noticeTypeCode;@JsonKey(name: 'form_type') String? get formType;@JsonKey(name: 'notice_language_code') String? get noticeLanguageCode;@JsonKey(name: 'issue_date', fromJson: unixTimestampFromJson) int? get issueDate;@JsonKey(name: 'issue_time', fromJson: unixTimestampFromJson) int? get issueTime;@JsonKey(name: 'publication_date', fromJson: unixTimestampFromJson) int? get publicationDate; String? get title; String? get description;@JsonKey(name: 'procurement_type_code') String? get procurementTypeCode;@JsonKey(name: 'procedure_code') String? get procedureCode;@JsonKey(name: 'estimated_value') int? get estimatedValue; String? get currency; String? get duration;@JsonKey(name: 'tender_deadline', fromJson: unixTimestampFromJson) int? get tenderDeadline;@JsonKey(name: 'submission_deadline', fromJson: unixTimestampFromJson) int? get submissionDeadline;@JsonKey(name: 'application_deadline', fromJson: unixTimestampFromJson) int? get applicationDeadline;@JsonKey(name: 'submission_url') String? get submissionUrl;@JsonKey(name: 'country_code') String? get countryCode;@JsonKey(name: 'duration_unit') String? get durationUnit;@JsonKey(name: 'buyer_organization') Organization? get buyerOrganization; List<Lot>? get lots;@JsonKey(name: 'official_languages') List<String>? get officialLanguages; String? get status;
/// Create a copy of TenderData
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -28,16 +28,16 @@ $TenderDataCopyWith<TenderData> get copyWith => _$TenderDataCopyWithImpl<TenderD
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is TenderData&&(identical(other.success, success) || other.success == success)&&(identical(other.message, message) || other.message == message)&&(identical(other.id, id) || other.id == id)&&(identical(other.tenderId, tenderId) || other.tenderId == tenderId)&&(identical(other.noticePublicationId, noticePublicationId) || other.noticePublicationId == noticePublicationId)&&(identical(other.publicationDate, publicationDate) || other.publicationDate == publicationDate)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.procurementTypeCode, procurementTypeCode) || other.procurementTypeCode == procurementTypeCode)&&(identical(other.procedureCode, procedureCode) || other.procedureCode == procedureCode)&&(identical(other.estimatedValue, estimatedValue) || other.estimatedValue == estimatedValue)&&(identical(other.currency, currency) || other.currency == currency)&&(identical(other.duration, duration) || other.duration == duration)&&(identical(other.tenderDeadline, tenderDeadline) || other.tenderDeadline == tenderDeadline)&&(identical(other.submissionDeadline, submissionDeadline) || other.submissionDeadline == submissionDeadline)&&(identical(other.applicationDeadline, applicationDeadline) || other.applicationDeadline == applicationDeadline)&&(identical(other.submissionUrl, submissionUrl) || other.submissionUrl == submissionUrl)&&(identical(other.countryCode, countryCode) || other.countryCode == countryCode)&&(identical(other.durationUnit, durationUnit) || other.durationUnit == durationUnit)&&(identical(other.buyerOrganization, buyerOrganization) || other.buyerOrganization == buyerOrganization)&&(identical(other.status, status) || other.status == status));
return identical(this, other) || (other.runtimeType == runtimeType&&other is TenderData&&(identical(other.success, success) || other.success == success)&&(identical(other.message, message) || other.message == message)&&(identical(other.id, id) || other.id == id)&&(identical(other.tenderId, tenderId) || other.tenderId == tenderId)&&(identical(other.noticePublicationId, noticePublicationId) || other.noticePublicationId == noticePublicationId)&&(identical(other.noticeTypeCode, noticeTypeCode) || other.noticeTypeCode == noticeTypeCode)&&(identical(other.formType, formType) || other.formType == formType)&&(identical(other.noticeLanguageCode, noticeLanguageCode) || other.noticeLanguageCode == noticeLanguageCode)&&(identical(other.issueDate, issueDate) || other.issueDate == issueDate)&&(identical(other.issueTime, issueTime) || other.issueTime == issueTime)&&(identical(other.publicationDate, publicationDate) || other.publicationDate == publicationDate)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.procurementTypeCode, procurementTypeCode) || other.procurementTypeCode == procurementTypeCode)&&(identical(other.procedureCode, procedureCode) || other.procedureCode == procedureCode)&&(identical(other.estimatedValue, estimatedValue) || other.estimatedValue == estimatedValue)&&(identical(other.currency, currency) || other.currency == currency)&&(identical(other.duration, duration) || other.duration == duration)&&(identical(other.tenderDeadline, tenderDeadline) || other.tenderDeadline == tenderDeadline)&&(identical(other.submissionDeadline, submissionDeadline) || other.submissionDeadline == submissionDeadline)&&(identical(other.applicationDeadline, applicationDeadline) || other.applicationDeadline == applicationDeadline)&&(identical(other.submissionUrl, submissionUrl) || other.submissionUrl == submissionUrl)&&(identical(other.countryCode, countryCode) || other.countryCode == countryCode)&&(identical(other.durationUnit, durationUnit) || other.durationUnit == durationUnit)&&(identical(other.buyerOrganization, buyerOrganization) || other.buyerOrganization == buyerOrganization)&&const DeepCollectionEquality().equals(other.lots, lots)&&const DeepCollectionEquality().equals(other.officialLanguages, officialLanguages)&&(identical(other.status, status) || other.status == status));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hashAll([runtimeType,success,message,id,tenderId,noticePublicationId,publicationDate,title,description,procurementTypeCode,procedureCode,estimatedValue,currency,duration,tenderDeadline,submissionDeadline,applicationDeadline,submissionUrl,countryCode,durationUnit,buyerOrganization,status]);
int get hashCode => Object.hashAll([runtimeType,success,message,id,tenderId,noticePublicationId,noticeTypeCode,formType,noticeLanguageCode,issueDate,issueTime,publicationDate,title,description,procurementTypeCode,procedureCode,estimatedValue,currency,duration,tenderDeadline,submissionDeadline,applicationDeadline,submissionUrl,countryCode,durationUnit,buyerOrganization,const DeepCollectionEquality().hash(lots),const DeepCollectionEquality().hash(officialLanguages),status]);
@override
String toString() {
return 'TenderData(success: $success, message: $message, id: $id, tenderId: $tenderId, noticePublicationId: $noticePublicationId, publicationDate: $publicationDate, title: $title, description: $description, procurementTypeCode: $procurementTypeCode, procedureCode: $procedureCode, estimatedValue: $estimatedValue, currency: $currency, duration: $duration, tenderDeadline: $tenderDeadline, submissionDeadline: $submissionDeadline, applicationDeadline: $applicationDeadline, submissionUrl: $submissionUrl, countryCode: $countryCode, durationUnit: $durationUnit, buyerOrganization: $buyerOrganization, status: $status)';
return 'TenderData(success: $success, message: $message, id: $id, tenderId: $tenderId, noticePublicationId: $noticePublicationId, noticeTypeCode: $noticeTypeCode, formType: $formType, noticeLanguageCode: $noticeLanguageCode, issueDate: $issueDate, issueTime: $issueTime, publicationDate: $publicationDate, title: $title, description: $description, procurementTypeCode: $procurementTypeCode, procedureCode: $procedureCode, estimatedValue: $estimatedValue, currency: $currency, duration: $duration, tenderDeadline: $tenderDeadline, submissionDeadline: $submissionDeadline, applicationDeadline: $applicationDeadline, submissionUrl: $submissionUrl, countryCode: $countryCode, durationUnit: $durationUnit, buyerOrganization: $buyerOrganization, lots: $lots, officialLanguages: $officialLanguages, status: $status)';
}
@@ -48,7 +48,7 @@ abstract mixin class $TenderDataCopyWith<$Res> {
factory $TenderDataCopyWith(TenderData value, $Res Function(TenderData) _then) = _$TenderDataCopyWithImpl;
@useResult
$Res call({
bool? success, String? message, String? id,@JsonKey(name: 'tender_id') String? tenderId,@JsonKey(name: 'notice_publication_id') String? noticePublicationId,@JsonKey(name: 'publication_date') int? publicationDate, String? title, String? description,@JsonKey(name: 'procurement_type_code') String? procurementTypeCode,@JsonKey(name: 'procedure_code') String? procedureCode,@JsonKey(name: 'estimated_value') int? estimatedValue, String? currency, String? duration,@JsonKey(name: 'tender_deadline') int? tenderDeadline,@JsonKey(name: 'submission_deadline') int? submissionDeadline,@JsonKey(name: 'application_deadline') int? applicationDeadline,@JsonKey(name: 'submission_url') String? submissionUrl,@JsonKey(name: 'country_code') String? countryCode,@JsonKey(name: 'duration_unit') String? durationUnit,@JsonKey(name: 'buyer_organization') Organization? buyerOrganization, String? status
bool? success, String? message, String? id,@JsonKey(name: 'tender_id') String? tenderId,@JsonKey(name: 'notice_publication_id') String? noticePublicationId,@JsonKey(name: 'notice_type_code') String? noticeTypeCode,@JsonKey(name: 'form_type') String? formType,@JsonKey(name: 'notice_language_code') String? noticeLanguageCode,@JsonKey(name: 'issue_date', fromJson: unixTimestampFromJson) int? issueDate,@JsonKey(name: 'issue_time', fromJson: unixTimestampFromJson) int? issueTime,@JsonKey(name: 'publication_date', fromJson: unixTimestampFromJson) int? publicationDate, String? title, String? description,@JsonKey(name: 'procurement_type_code') String? procurementTypeCode,@JsonKey(name: 'procedure_code') String? procedureCode,@JsonKey(name: 'estimated_value') int? estimatedValue, String? currency, String? duration,@JsonKey(name: 'tender_deadline', fromJson: unixTimestampFromJson) int? tenderDeadline,@JsonKey(name: 'submission_deadline', fromJson: unixTimestampFromJson) int? submissionDeadline,@JsonKey(name: 'application_deadline', fromJson: unixTimestampFromJson) int? applicationDeadline,@JsonKey(name: 'submission_url') String? submissionUrl,@JsonKey(name: 'country_code') String? countryCode,@JsonKey(name: 'duration_unit') String? durationUnit,@JsonKey(name: 'buyer_organization') Organization? buyerOrganization, List<Lot>? lots,@JsonKey(name: 'official_languages') List<String>? officialLanguages, String? status
});
@@ -65,14 +65,19 @@ class _$TenderDataCopyWithImpl<$Res>
/// Create a copy of TenderData
/// 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? id = freezed,Object? tenderId = freezed,Object? noticePublicationId = freezed,Object? publicationDate = freezed,Object? title = freezed,Object? description = freezed,Object? procurementTypeCode = freezed,Object? procedureCode = freezed,Object? estimatedValue = freezed,Object? currency = freezed,Object? duration = freezed,Object? tenderDeadline = freezed,Object? submissionDeadline = freezed,Object? applicationDeadline = freezed,Object? submissionUrl = freezed,Object? countryCode = freezed,Object? durationUnit = freezed,Object? buyerOrganization = freezed,Object? status = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? success = freezed,Object? message = freezed,Object? id = freezed,Object? tenderId = freezed,Object? noticePublicationId = freezed,Object? noticeTypeCode = freezed,Object? formType = freezed,Object? noticeLanguageCode = freezed,Object? issueDate = freezed,Object? issueTime = freezed,Object? publicationDate = freezed,Object? title = freezed,Object? description = freezed,Object? procurementTypeCode = freezed,Object? procedureCode = freezed,Object? estimatedValue = freezed,Object? currency = freezed,Object? duration = freezed,Object? tenderDeadline = freezed,Object? submissionDeadline = freezed,Object? applicationDeadline = freezed,Object? submissionUrl = freezed,Object? countryCode = freezed,Object? durationUnit = freezed,Object? buyerOrganization = freezed,Object? lots = freezed,Object? officialLanguages = freezed,Object? status = 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?,id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String?,tenderId: freezed == tenderId ? _self.tenderId : tenderId // ignore: cast_nullable_to_non_nullable
as String?,noticePublicationId: freezed == noticePublicationId ? _self.noticePublicationId : noticePublicationId // ignore: cast_nullable_to_non_nullable
as String?,publicationDate: freezed == publicationDate ? _self.publicationDate : publicationDate // ignore: cast_nullable_to_non_nullable
as String?,noticeTypeCode: freezed == noticeTypeCode ? _self.noticeTypeCode : noticeTypeCode // ignore: cast_nullable_to_non_nullable
as String?,formType: freezed == formType ? _self.formType : formType // ignore: cast_nullable_to_non_nullable
as String?,noticeLanguageCode: freezed == noticeLanguageCode ? _self.noticeLanguageCode : noticeLanguageCode // ignore: cast_nullable_to_non_nullable
as String?,issueDate: freezed == issueDate ? _self.issueDate : issueDate // ignore: cast_nullable_to_non_nullable
as int?,issueTime: freezed == issueTime ? _self.issueTime : issueTime // ignore: cast_nullable_to_non_nullable
as int?,publicationDate: freezed == publicationDate ? _self.publicationDate : publicationDate // ignore: cast_nullable_to_non_nullable
as int?,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String?,procurementTypeCode: freezed == procurementTypeCode ? _self.procurementTypeCode : procurementTypeCode // ignore: cast_nullable_to_non_nullable
@@ -87,7 +92,9 @@ as int?,submissionUrl: freezed == submissionUrl ? _self.submissionUrl : submissi
as String?,countryCode: freezed == countryCode ? _self.countryCode : countryCode // ignore: cast_nullable_to_non_nullable
as String?,durationUnit: freezed == durationUnit ? _self.durationUnit : durationUnit // ignore: cast_nullable_to_non_nullable
as String?,buyerOrganization: freezed == buyerOrganization ? _self.buyerOrganization : buyerOrganization // ignore: cast_nullable_to_non_nullable
as Organization?,status: freezed == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
as Organization?,lots: freezed == lots ? _self.lots : lots // ignore: cast_nullable_to_non_nullable
as List<Lot>?,officialLanguages: freezed == officialLanguages ? _self.officialLanguages : officialLanguages // ignore: cast_nullable_to_non_nullable
as List<String>?,status: freezed == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
as String?,
));
}
@@ -185,10 +192,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool? success, String? message, String? id, @JsonKey(name: 'tender_id') String? tenderId, @JsonKey(name: 'notice_publication_id') String? noticePublicationId, @JsonKey(name: 'publication_date') int? publicationDate, String? title, String? description, @JsonKey(name: 'procurement_type_code') String? procurementTypeCode, @JsonKey(name: 'procedure_code') String? procedureCode, @JsonKey(name: 'estimated_value') int? estimatedValue, String? currency, String? duration, @JsonKey(name: 'tender_deadline') int? tenderDeadline, @JsonKey(name: 'submission_deadline') int? submissionDeadline, @JsonKey(name: 'application_deadline') int? applicationDeadline, @JsonKey(name: 'submission_url') String? submissionUrl, @JsonKey(name: 'country_code') String? countryCode, @JsonKey(name: 'duration_unit') String? durationUnit, @JsonKey(name: 'buyer_organization') Organization? buyerOrganization, String? status)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool? success, String? message, String? id, @JsonKey(name: 'tender_id') String? tenderId, @JsonKey(name: 'notice_publication_id') String? noticePublicationId, @JsonKey(name: 'notice_type_code') String? noticeTypeCode, @JsonKey(name: 'form_type') String? formType, @JsonKey(name: 'notice_language_code') String? noticeLanguageCode, @JsonKey(name: 'issue_date', fromJson: unixTimestampFromJson) int? issueDate, @JsonKey(name: 'issue_time', fromJson: unixTimestampFromJson) int? issueTime, @JsonKey(name: 'publication_date', fromJson: unixTimestampFromJson) int? publicationDate, String? title, String? description, @JsonKey(name: 'procurement_type_code') String? procurementTypeCode, @JsonKey(name: 'procedure_code') String? procedureCode, @JsonKey(name: 'estimated_value') int? estimatedValue, String? currency, String? duration, @JsonKey(name: 'tender_deadline', fromJson: unixTimestampFromJson) int? tenderDeadline, @JsonKey(name: 'submission_deadline', fromJson: unixTimestampFromJson) int? submissionDeadline, @JsonKey(name: 'application_deadline', fromJson: unixTimestampFromJson) int? applicationDeadline, @JsonKey(name: 'submission_url') String? submissionUrl, @JsonKey(name: 'country_code') String? countryCode, @JsonKey(name: 'duration_unit') String? durationUnit, @JsonKey(name: 'buyer_organization') Organization? buyerOrganization, List<Lot>? lots, @JsonKey(name: 'official_languages') List<String>? officialLanguages, String? status)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _TenderData() when $default != null:
return $default(_that.success,_that.message,_that.id,_that.tenderId,_that.noticePublicationId,_that.publicationDate,_that.title,_that.description,_that.procurementTypeCode,_that.procedureCode,_that.estimatedValue,_that.currency,_that.duration,_that.tenderDeadline,_that.submissionDeadline,_that.applicationDeadline,_that.submissionUrl,_that.countryCode,_that.durationUnit,_that.buyerOrganization,_that.status);case _:
return $default(_that.success,_that.message,_that.id,_that.tenderId,_that.noticePublicationId,_that.noticeTypeCode,_that.formType,_that.noticeLanguageCode,_that.issueDate,_that.issueTime,_that.publicationDate,_that.title,_that.description,_that.procurementTypeCode,_that.procedureCode,_that.estimatedValue,_that.currency,_that.duration,_that.tenderDeadline,_that.submissionDeadline,_that.applicationDeadline,_that.submissionUrl,_that.countryCode,_that.durationUnit,_that.buyerOrganization,_that.lots,_that.officialLanguages,_that.status);case _:
return orElse();
}
@@ -206,10 +213,10 @@ return $default(_that.success,_that.message,_that.id,_that.tenderId,_that.notice
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool? success, String? message, String? id, @JsonKey(name: 'tender_id') String? tenderId, @JsonKey(name: 'notice_publication_id') String? noticePublicationId, @JsonKey(name: 'publication_date') int? publicationDate, String? title, String? description, @JsonKey(name: 'procurement_type_code') String? procurementTypeCode, @JsonKey(name: 'procedure_code') String? procedureCode, @JsonKey(name: 'estimated_value') int? estimatedValue, String? currency, String? duration, @JsonKey(name: 'tender_deadline') int? tenderDeadline, @JsonKey(name: 'submission_deadline') int? submissionDeadline, @JsonKey(name: 'application_deadline') int? applicationDeadline, @JsonKey(name: 'submission_url') String? submissionUrl, @JsonKey(name: 'country_code') String? countryCode, @JsonKey(name: 'duration_unit') String? durationUnit, @JsonKey(name: 'buyer_organization') Organization? buyerOrganization, String? status) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool? success, String? message, String? id, @JsonKey(name: 'tender_id') String? tenderId, @JsonKey(name: 'notice_publication_id') String? noticePublicationId, @JsonKey(name: 'notice_type_code') String? noticeTypeCode, @JsonKey(name: 'form_type') String? formType, @JsonKey(name: 'notice_language_code') String? noticeLanguageCode, @JsonKey(name: 'issue_date', fromJson: unixTimestampFromJson) int? issueDate, @JsonKey(name: 'issue_time', fromJson: unixTimestampFromJson) int? issueTime, @JsonKey(name: 'publication_date', fromJson: unixTimestampFromJson) int? publicationDate, String? title, String? description, @JsonKey(name: 'procurement_type_code') String? procurementTypeCode, @JsonKey(name: 'procedure_code') String? procedureCode, @JsonKey(name: 'estimated_value') int? estimatedValue, String? currency, String? duration, @JsonKey(name: 'tender_deadline', fromJson: unixTimestampFromJson) int? tenderDeadline, @JsonKey(name: 'submission_deadline', fromJson: unixTimestampFromJson) int? submissionDeadline, @JsonKey(name: 'application_deadline', fromJson: unixTimestampFromJson) int? applicationDeadline, @JsonKey(name: 'submission_url') String? submissionUrl, @JsonKey(name: 'country_code') String? countryCode, @JsonKey(name: 'duration_unit') String? durationUnit, @JsonKey(name: 'buyer_organization') Organization? buyerOrganization, List<Lot>? lots, @JsonKey(name: 'official_languages') List<String>? officialLanguages, String? status) $default,) {final _that = this;
switch (_that) {
case _TenderData():
return $default(_that.success,_that.message,_that.id,_that.tenderId,_that.noticePublicationId,_that.publicationDate,_that.title,_that.description,_that.procurementTypeCode,_that.procedureCode,_that.estimatedValue,_that.currency,_that.duration,_that.tenderDeadline,_that.submissionDeadline,_that.applicationDeadline,_that.submissionUrl,_that.countryCode,_that.durationUnit,_that.buyerOrganization,_that.status);case _:
return $default(_that.success,_that.message,_that.id,_that.tenderId,_that.noticePublicationId,_that.noticeTypeCode,_that.formType,_that.noticeLanguageCode,_that.issueDate,_that.issueTime,_that.publicationDate,_that.title,_that.description,_that.procurementTypeCode,_that.procedureCode,_that.estimatedValue,_that.currency,_that.duration,_that.tenderDeadline,_that.submissionDeadline,_that.applicationDeadline,_that.submissionUrl,_that.countryCode,_that.durationUnit,_that.buyerOrganization,_that.lots,_that.officialLanguages,_that.status);case _:
throw StateError('Unexpected subclass');
}
@@ -226,10 +233,10 @@ return $default(_that.success,_that.message,_that.id,_that.tenderId,_that.notice
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool? success, String? message, String? id, @JsonKey(name: 'tender_id') String? tenderId, @JsonKey(name: 'notice_publication_id') String? noticePublicationId, @JsonKey(name: 'publication_date') int? publicationDate, String? title, String? description, @JsonKey(name: 'procurement_type_code') String? procurementTypeCode, @JsonKey(name: 'procedure_code') String? procedureCode, @JsonKey(name: 'estimated_value') int? estimatedValue, String? currency, String? duration, @JsonKey(name: 'tender_deadline') int? tenderDeadline, @JsonKey(name: 'submission_deadline') int? submissionDeadline, @JsonKey(name: 'application_deadline') int? applicationDeadline, @JsonKey(name: 'submission_url') String? submissionUrl, @JsonKey(name: 'country_code') String? countryCode, @JsonKey(name: 'duration_unit') String? durationUnit, @JsonKey(name: 'buyer_organization') Organization? buyerOrganization, String? status)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool? success, String? message, String? id, @JsonKey(name: 'tender_id') String? tenderId, @JsonKey(name: 'notice_publication_id') String? noticePublicationId, @JsonKey(name: 'notice_type_code') String? noticeTypeCode, @JsonKey(name: 'form_type') String? formType, @JsonKey(name: 'notice_language_code') String? noticeLanguageCode, @JsonKey(name: 'issue_date', fromJson: unixTimestampFromJson) int? issueDate, @JsonKey(name: 'issue_time', fromJson: unixTimestampFromJson) int? issueTime, @JsonKey(name: 'publication_date', fromJson: unixTimestampFromJson) int? publicationDate, String? title, String? description, @JsonKey(name: 'procurement_type_code') String? procurementTypeCode, @JsonKey(name: 'procedure_code') String? procedureCode, @JsonKey(name: 'estimated_value') int? estimatedValue, String? currency, String? duration, @JsonKey(name: 'tender_deadline', fromJson: unixTimestampFromJson) int? tenderDeadline, @JsonKey(name: 'submission_deadline', fromJson: unixTimestampFromJson) int? submissionDeadline, @JsonKey(name: 'application_deadline', fromJson: unixTimestampFromJson) int? applicationDeadline, @JsonKey(name: 'submission_url') String? submissionUrl, @JsonKey(name: 'country_code') String? countryCode, @JsonKey(name: 'duration_unit') String? durationUnit, @JsonKey(name: 'buyer_organization') Organization? buyerOrganization, List<Lot>? lots, @JsonKey(name: 'official_languages') List<String>? officialLanguages, String? status)? $default,) {final _that = this;
switch (_that) {
case _TenderData() when $default != null:
return $default(_that.success,_that.message,_that.id,_that.tenderId,_that.noticePublicationId,_that.publicationDate,_that.title,_that.description,_that.procurementTypeCode,_that.procedureCode,_that.estimatedValue,_that.currency,_that.duration,_that.tenderDeadline,_that.submissionDeadline,_that.applicationDeadline,_that.submissionUrl,_that.countryCode,_that.durationUnit,_that.buyerOrganization,_that.status);case _:
return $default(_that.success,_that.message,_that.id,_that.tenderId,_that.noticePublicationId,_that.noticeTypeCode,_that.formType,_that.noticeLanguageCode,_that.issueDate,_that.issueTime,_that.publicationDate,_that.title,_that.description,_that.procurementTypeCode,_that.procedureCode,_that.estimatedValue,_that.currency,_that.duration,_that.tenderDeadline,_that.submissionDeadline,_that.applicationDeadline,_that.submissionUrl,_that.countryCode,_that.durationUnit,_that.buyerOrganization,_that.lots,_that.officialLanguages,_that.status);case _:
return null;
}
@@ -241,7 +248,7 @@ return $default(_that.success,_that.message,_that.id,_that.tenderId,_that.notice
@JsonSerializable()
class _TenderData implements TenderData {
const _TenderData({required this.success, required this.message, required this.id, @JsonKey(name: 'tender_id') required this.tenderId, @JsonKey(name: 'notice_publication_id') required this.noticePublicationId, @JsonKey(name: 'publication_date') required this.publicationDate, required this.title, required this.description, @JsonKey(name: 'procurement_type_code') required this.procurementTypeCode, @JsonKey(name: 'procedure_code') required this.procedureCode, @JsonKey(name: 'estimated_value') required this.estimatedValue, required this.currency, required this.duration, @JsonKey(name: 'tender_deadline') required this.tenderDeadline, @JsonKey(name: 'submission_deadline') required this.submissionDeadline, @JsonKey(name: 'application_deadline') required this.applicationDeadline, @JsonKey(name: 'submission_url') required this.submissionUrl, @JsonKey(name: 'country_code') required this.countryCode, @JsonKey(name: 'duration_unit') required this.durationUnit, @JsonKey(name: 'buyer_organization') required this.buyerOrganization, required this.status});
const _TenderData({required this.success, required this.message, required this.id, @JsonKey(name: 'tender_id') required this.tenderId, @JsonKey(name: 'notice_publication_id') required this.noticePublicationId, @JsonKey(name: 'notice_type_code') required this.noticeTypeCode, @JsonKey(name: 'form_type') required this.formType, @JsonKey(name: 'notice_language_code') required this.noticeLanguageCode, @JsonKey(name: 'issue_date', fromJson: unixTimestampFromJson) required this.issueDate, @JsonKey(name: 'issue_time', fromJson: unixTimestampFromJson) required this.issueTime, @JsonKey(name: 'publication_date', fromJson: unixTimestampFromJson) required this.publicationDate, required this.title, required this.description, @JsonKey(name: 'procurement_type_code') required this.procurementTypeCode, @JsonKey(name: 'procedure_code') required this.procedureCode, @JsonKey(name: 'estimated_value') required this.estimatedValue, required this.currency, required this.duration, @JsonKey(name: 'tender_deadline', fromJson: unixTimestampFromJson) required this.tenderDeadline, @JsonKey(name: 'submission_deadline', fromJson: unixTimestampFromJson) required this.submissionDeadline, @JsonKey(name: 'application_deadline', fromJson: unixTimestampFromJson) required this.applicationDeadline, @JsonKey(name: 'submission_url') required this.submissionUrl, @JsonKey(name: 'country_code') required this.countryCode, @JsonKey(name: 'duration_unit') required this.durationUnit, @JsonKey(name: 'buyer_organization') required this.buyerOrganization, required final List<Lot>? lots, @JsonKey(name: 'official_languages') required final List<String>? officialLanguages, required this.status}): _lots = lots,_officialLanguages = officialLanguages;
factory _TenderData.fromJson(Map<String, dynamic> json) => _$TenderDataFromJson(json);
@override final bool? success;
@@ -249,7 +256,12 @@ class _TenderData implements TenderData {
@override final String? id;
@override@JsonKey(name: 'tender_id') final String? tenderId;
@override@JsonKey(name: 'notice_publication_id') final String? noticePublicationId;
@override@JsonKey(name: 'publication_date') final int? publicationDate;
@override@JsonKey(name: 'notice_type_code') final String? noticeTypeCode;
@override@JsonKey(name: 'form_type') final String? formType;
@override@JsonKey(name: 'notice_language_code') final String? noticeLanguageCode;
@override@JsonKey(name: 'issue_date', fromJson: unixTimestampFromJson) final int? issueDate;
@override@JsonKey(name: 'issue_time', fromJson: unixTimestampFromJson) final int? issueTime;
@override@JsonKey(name: 'publication_date', fromJson: unixTimestampFromJson) final int? publicationDate;
@override final String? title;
@override final String? description;
@override@JsonKey(name: 'procurement_type_code') final String? procurementTypeCode;
@@ -257,13 +269,31 @@ class _TenderData implements TenderData {
@override@JsonKey(name: 'estimated_value') final int? estimatedValue;
@override final String? currency;
@override final String? duration;
@override@JsonKey(name: 'tender_deadline') final int? tenderDeadline;
@override@JsonKey(name: 'submission_deadline') final int? submissionDeadline;
@override@JsonKey(name: 'application_deadline') final int? applicationDeadline;
@override@JsonKey(name: 'tender_deadline', fromJson: unixTimestampFromJson) final int? tenderDeadline;
@override@JsonKey(name: 'submission_deadline', fromJson: unixTimestampFromJson) final int? submissionDeadline;
@override@JsonKey(name: 'application_deadline', fromJson: unixTimestampFromJson) final int? applicationDeadline;
@override@JsonKey(name: 'submission_url') final String? submissionUrl;
@override@JsonKey(name: 'country_code') final String? countryCode;
@override@JsonKey(name: 'duration_unit') final String? durationUnit;
@override@JsonKey(name: 'buyer_organization') final Organization? buyerOrganization;
final List<Lot>? _lots;
@override List<Lot>? get lots {
final value = _lots;
if (value == null) return null;
if (_lots is EqualUnmodifiableListView) return _lots;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
final List<String>? _officialLanguages;
@override@JsonKey(name: 'official_languages') List<String>? get officialLanguages {
final value = _officialLanguages;
if (value == null) return null;
if (_officialLanguages is EqualUnmodifiableListView) return _officialLanguages;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(value);
}
@override final String? status;
/// Create a copy of TenderData
@@ -279,16 +309,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _TenderData&&(identical(other.success, success) || other.success == success)&&(identical(other.message, message) || other.message == message)&&(identical(other.id, id) || other.id == id)&&(identical(other.tenderId, tenderId) || other.tenderId == tenderId)&&(identical(other.noticePublicationId, noticePublicationId) || other.noticePublicationId == noticePublicationId)&&(identical(other.publicationDate, publicationDate) || other.publicationDate == publicationDate)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.procurementTypeCode, procurementTypeCode) || other.procurementTypeCode == procurementTypeCode)&&(identical(other.procedureCode, procedureCode) || other.procedureCode == procedureCode)&&(identical(other.estimatedValue, estimatedValue) || other.estimatedValue == estimatedValue)&&(identical(other.currency, currency) || other.currency == currency)&&(identical(other.duration, duration) || other.duration == duration)&&(identical(other.tenderDeadline, tenderDeadline) || other.tenderDeadline == tenderDeadline)&&(identical(other.submissionDeadline, submissionDeadline) || other.submissionDeadline == submissionDeadline)&&(identical(other.applicationDeadline, applicationDeadline) || other.applicationDeadline == applicationDeadline)&&(identical(other.submissionUrl, submissionUrl) || other.submissionUrl == submissionUrl)&&(identical(other.countryCode, countryCode) || other.countryCode == countryCode)&&(identical(other.durationUnit, durationUnit) || other.durationUnit == durationUnit)&&(identical(other.buyerOrganization, buyerOrganization) || other.buyerOrganization == buyerOrganization)&&(identical(other.status, status) || other.status == status));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _TenderData&&(identical(other.success, success) || other.success == success)&&(identical(other.message, message) || other.message == message)&&(identical(other.id, id) || other.id == id)&&(identical(other.tenderId, tenderId) || other.tenderId == tenderId)&&(identical(other.noticePublicationId, noticePublicationId) || other.noticePublicationId == noticePublicationId)&&(identical(other.noticeTypeCode, noticeTypeCode) || other.noticeTypeCode == noticeTypeCode)&&(identical(other.formType, formType) || other.formType == formType)&&(identical(other.noticeLanguageCode, noticeLanguageCode) || other.noticeLanguageCode == noticeLanguageCode)&&(identical(other.issueDate, issueDate) || other.issueDate == issueDate)&&(identical(other.issueTime, issueTime) || other.issueTime == issueTime)&&(identical(other.publicationDate, publicationDate) || other.publicationDate == publicationDate)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.procurementTypeCode, procurementTypeCode) || other.procurementTypeCode == procurementTypeCode)&&(identical(other.procedureCode, procedureCode) || other.procedureCode == procedureCode)&&(identical(other.estimatedValue, estimatedValue) || other.estimatedValue == estimatedValue)&&(identical(other.currency, currency) || other.currency == currency)&&(identical(other.duration, duration) || other.duration == duration)&&(identical(other.tenderDeadline, tenderDeadline) || other.tenderDeadline == tenderDeadline)&&(identical(other.submissionDeadline, submissionDeadline) || other.submissionDeadline == submissionDeadline)&&(identical(other.applicationDeadline, applicationDeadline) || other.applicationDeadline == applicationDeadline)&&(identical(other.submissionUrl, submissionUrl) || other.submissionUrl == submissionUrl)&&(identical(other.countryCode, countryCode) || other.countryCode == countryCode)&&(identical(other.durationUnit, durationUnit) || other.durationUnit == durationUnit)&&(identical(other.buyerOrganization, buyerOrganization) || other.buyerOrganization == buyerOrganization)&&const DeepCollectionEquality().equals(other._lots, _lots)&&const DeepCollectionEquality().equals(other._officialLanguages, _officialLanguages)&&(identical(other.status, status) || other.status == status));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hashAll([runtimeType,success,message,id,tenderId,noticePublicationId,publicationDate,title,description,procurementTypeCode,procedureCode,estimatedValue,currency,duration,tenderDeadline,submissionDeadline,applicationDeadline,submissionUrl,countryCode,durationUnit,buyerOrganization,status]);
int get hashCode => Object.hashAll([runtimeType,success,message,id,tenderId,noticePublicationId,noticeTypeCode,formType,noticeLanguageCode,issueDate,issueTime,publicationDate,title,description,procurementTypeCode,procedureCode,estimatedValue,currency,duration,tenderDeadline,submissionDeadline,applicationDeadline,submissionUrl,countryCode,durationUnit,buyerOrganization,const DeepCollectionEquality().hash(_lots),const DeepCollectionEquality().hash(_officialLanguages),status]);
@override
String toString() {
return 'TenderData(success: $success, message: $message, id: $id, tenderId: $tenderId, noticePublicationId: $noticePublicationId, publicationDate: $publicationDate, title: $title, description: $description, procurementTypeCode: $procurementTypeCode, procedureCode: $procedureCode, estimatedValue: $estimatedValue, currency: $currency, duration: $duration, tenderDeadline: $tenderDeadline, submissionDeadline: $submissionDeadline, applicationDeadline: $applicationDeadline, submissionUrl: $submissionUrl, countryCode: $countryCode, durationUnit: $durationUnit, buyerOrganization: $buyerOrganization, status: $status)';
return 'TenderData(success: $success, message: $message, id: $id, tenderId: $tenderId, noticePublicationId: $noticePublicationId, noticeTypeCode: $noticeTypeCode, formType: $formType, noticeLanguageCode: $noticeLanguageCode, issueDate: $issueDate, issueTime: $issueTime, publicationDate: $publicationDate, title: $title, description: $description, procurementTypeCode: $procurementTypeCode, procedureCode: $procedureCode, estimatedValue: $estimatedValue, currency: $currency, duration: $duration, tenderDeadline: $tenderDeadline, submissionDeadline: $submissionDeadline, applicationDeadline: $applicationDeadline, submissionUrl: $submissionUrl, countryCode: $countryCode, durationUnit: $durationUnit, buyerOrganization: $buyerOrganization, lots: $lots, officialLanguages: $officialLanguages, status: $status)';
}
@@ -299,7 +329,7 @@ abstract mixin class _$TenderDataCopyWith<$Res> implements $TenderDataCopyWith<$
factory _$TenderDataCopyWith(_TenderData value, $Res Function(_TenderData) _then) = __$TenderDataCopyWithImpl;
@override @useResult
$Res call({
bool? success, String? message, String? id,@JsonKey(name: 'tender_id') String? tenderId,@JsonKey(name: 'notice_publication_id') String? noticePublicationId,@JsonKey(name: 'publication_date') int? publicationDate, String? title, String? description,@JsonKey(name: 'procurement_type_code') String? procurementTypeCode,@JsonKey(name: 'procedure_code') String? procedureCode,@JsonKey(name: 'estimated_value') int? estimatedValue, String? currency, String? duration,@JsonKey(name: 'tender_deadline') int? tenderDeadline,@JsonKey(name: 'submission_deadline') int? submissionDeadline,@JsonKey(name: 'application_deadline') int? applicationDeadline,@JsonKey(name: 'submission_url') String? submissionUrl,@JsonKey(name: 'country_code') String? countryCode,@JsonKey(name: 'duration_unit') String? durationUnit,@JsonKey(name: 'buyer_organization') Organization? buyerOrganization, String? status
bool? success, String? message, String? id,@JsonKey(name: 'tender_id') String? tenderId,@JsonKey(name: 'notice_publication_id') String? noticePublicationId,@JsonKey(name: 'notice_type_code') String? noticeTypeCode,@JsonKey(name: 'form_type') String? formType,@JsonKey(name: 'notice_language_code') String? noticeLanguageCode,@JsonKey(name: 'issue_date', fromJson: unixTimestampFromJson) int? issueDate,@JsonKey(name: 'issue_time', fromJson: unixTimestampFromJson) int? issueTime,@JsonKey(name: 'publication_date', fromJson: unixTimestampFromJson) int? publicationDate, String? title, String? description,@JsonKey(name: 'procurement_type_code') String? procurementTypeCode,@JsonKey(name: 'procedure_code') String? procedureCode,@JsonKey(name: 'estimated_value') int? estimatedValue, String? currency, String? duration,@JsonKey(name: 'tender_deadline', fromJson: unixTimestampFromJson) int? tenderDeadline,@JsonKey(name: 'submission_deadline', fromJson: unixTimestampFromJson) int? submissionDeadline,@JsonKey(name: 'application_deadline', fromJson: unixTimestampFromJson) int? applicationDeadline,@JsonKey(name: 'submission_url') String? submissionUrl,@JsonKey(name: 'country_code') String? countryCode,@JsonKey(name: 'duration_unit') String? durationUnit,@JsonKey(name: 'buyer_organization') Organization? buyerOrganization, List<Lot>? lots,@JsonKey(name: 'official_languages') List<String>? officialLanguages, String? status
});
@@ -316,14 +346,19 @@ class __$TenderDataCopyWithImpl<$Res>
/// Create a copy of TenderData
/// 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? id = freezed,Object? tenderId = freezed,Object? noticePublicationId = freezed,Object? publicationDate = freezed,Object? title = freezed,Object? description = freezed,Object? procurementTypeCode = freezed,Object? procedureCode = freezed,Object? estimatedValue = freezed,Object? currency = freezed,Object? duration = freezed,Object? tenderDeadline = freezed,Object? submissionDeadline = freezed,Object? applicationDeadline = freezed,Object? submissionUrl = freezed,Object? countryCode = freezed,Object? durationUnit = freezed,Object? buyerOrganization = freezed,Object? status = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? success = freezed,Object? message = freezed,Object? id = freezed,Object? tenderId = freezed,Object? noticePublicationId = freezed,Object? noticeTypeCode = freezed,Object? formType = freezed,Object? noticeLanguageCode = freezed,Object? issueDate = freezed,Object? issueTime = freezed,Object? publicationDate = freezed,Object? title = freezed,Object? description = freezed,Object? procurementTypeCode = freezed,Object? procedureCode = freezed,Object? estimatedValue = freezed,Object? currency = freezed,Object? duration = freezed,Object? tenderDeadline = freezed,Object? submissionDeadline = freezed,Object? applicationDeadline = freezed,Object? submissionUrl = freezed,Object? countryCode = freezed,Object? durationUnit = freezed,Object? buyerOrganization = freezed,Object? lots = freezed,Object? officialLanguages = freezed,Object? status = freezed,}) {
return _then(_TenderData(
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?,id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String?,tenderId: freezed == tenderId ? _self.tenderId : tenderId // ignore: cast_nullable_to_non_nullable
as String?,noticePublicationId: freezed == noticePublicationId ? _self.noticePublicationId : noticePublicationId // ignore: cast_nullable_to_non_nullable
as String?,publicationDate: freezed == publicationDate ? _self.publicationDate : publicationDate // ignore: cast_nullable_to_non_nullable
as String?,noticeTypeCode: freezed == noticeTypeCode ? _self.noticeTypeCode : noticeTypeCode // ignore: cast_nullable_to_non_nullable
as String?,formType: freezed == formType ? _self.formType : formType // ignore: cast_nullable_to_non_nullable
as String?,noticeLanguageCode: freezed == noticeLanguageCode ? _self.noticeLanguageCode : noticeLanguageCode // ignore: cast_nullable_to_non_nullable
as String?,issueDate: freezed == issueDate ? _self.issueDate : issueDate // ignore: cast_nullable_to_non_nullable
as int?,issueTime: freezed == issueTime ? _self.issueTime : issueTime // ignore: cast_nullable_to_non_nullable
as int?,publicationDate: freezed == publicationDate ? _self.publicationDate : publicationDate // ignore: cast_nullable_to_non_nullable
as int?,title: freezed == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
as String?,procurementTypeCode: freezed == procurementTypeCode ? _self.procurementTypeCode : procurementTypeCode // ignore: cast_nullable_to_non_nullable
@@ -338,7 +373,9 @@ as int?,submissionUrl: freezed == submissionUrl ? _self.submissionUrl : submissi
as String?,countryCode: freezed == countryCode ? _self.countryCode : countryCode // ignore: cast_nullable_to_non_nullable
as String?,durationUnit: freezed == durationUnit ? _self.durationUnit : durationUnit // ignore: cast_nullable_to_non_nullable
as String?,buyerOrganization: freezed == buyerOrganization ? _self.buyerOrganization : buyerOrganization // ignore: cast_nullable_to_non_nullable
as Organization?,status: freezed == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
as Organization?,lots: freezed == lots ? _self._lots : lots // ignore: cast_nullable_to_non_nullable
as List<Lot>?,officialLanguages: freezed == officialLanguages ? _self._officialLanguages : officialLanguages // ignore: cast_nullable_to_non_nullable
as List<String>?,status: freezed == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
as String?,
));
}
@@ -12,6 +12,11 @@ _TenderData _$TenderDataFromJson(Map<String, dynamic> json) => _TenderData(
id: json['id'] as String?,
tenderId: json['tender_id'] as String?,
noticePublicationId: json['notice_publication_id'] as String?,
noticeTypeCode: json['notice_type_code'] as String?,
formType: json['form_type'] as String?,
noticeLanguageCode: json['notice_language_code'] as String?,
issueDate: unixTimestampFromJson(json['issue_date']),
issueTime: unixTimestampFromJson(json['issue_time']),
publicationDate: unixTimestampFromJson(json['publication_date']),
title: json['title'] as String?,
description: json['description'] as String?,
@@ -32,6 +37,14 @@ _TenderData _$TenderDataFromJson(Map<String, dynamic> json) => _TenderData(
: Organization.fromJson(
json['buyer_organization'] as Map<String, dynamic>,
),
lots:
(json['lots'] as List<dynamic>?)
?.map((e) => Lot.fromJson(e as Map<String, dynamic>))
.toList(),
officialLanguages:
(json['official_languages'] as List<dynamic>?)
?.map((e) => e as String)
.toList(),
status: json['status'] as String?,
);
@@ -42,6 +55,11 @@ Map<String, dynamic> _$TenderDataToJson(_TenderData instance) =>
'id': instance.id,
'tender_id': instance.tenderId,
'notice_publication_id': instance.noticePublicationId,
'notice_type_code': instance.noticeTypeCode,
'form_type': instance.formType,
'notice_language_code': instance.noticeLanguageCode,
'issue_date': instance.issueDate,
'issue_time': instance.issueTime,
'publication_date': instance.publicationDate,
'title': instance.title,
'description': instance.description,
@@ -57,5 +75,7 @@ Map<String, dynamic> _$TenderDataToJson(_TenderData instance) =>
'country_code': instance.countryCode,
'duration_unit': instance.durationUnit,
'buyer_organization': instance.buyerOrganization,
'lots': instance.lots,
'official_languages': instance.officialLanguages,
'status': instance.status,
};
+33
View File
@@ -11,6 +11,38 @@
import 'package:flutter/widgets.dart';
class $AssetsFontsGen {
const $AssetsFontsGen();
/// File path: assets/fonts/Roboto-Black.ttf
String get robotoBlack => 'assets/fonts/Roboto-Black.ttf';
/// File path: assets/fonts/Roboto-Bold.ttf
String get robotoBold => 'assets/fonts/Roboto-Bold.ttf';
/// File path: assets/fonts/Roboto-Light.ttf
String get robotoLight => 'assets/fonts/Roboto-Light.ttf';
/// File path: assets/fonts/Roboto-Medium.ttf
String get robotoMedium => 'assets/fonts/Roboto-Medium.ttf';
/// File path: assets/fonts/Roboto-Regular.ttf
String get robotoRegular => 'assets/fonts/Roboto-Regular.ttf';
/// File path: assets/fonts/Roboto-Thin.ttf
String get robotoThin => 'assets/fonts/Roboto-Thin.ttf';
/// List of all assets
List<String> get values => [
robotoBlack,
robotoBold,
robotoLight,
robotoMedium,
robotoRegular,
robotoThin,
];
}
class $AssetsIconsGen {
const $AssetsIconsGen();
@@ -301,6 +333,7 @@ class $AssetsSvgsGen {
class Assets {
const Assets._();
static const $AssetsFontsGen fonts = $AssetsFontsGen();
static const $AssetsIconsGen icons = $AssetsIconsGen();
static const $AssetsPngsGen pngs = $AssetsPngsGen();
static const $AssetsSvgsGen svgs = $AssetsSvgsGen();
+16
View File
@@ -0,0 +1,16 @@
// dart format width=80
/// GENERATED CODE - DO NOT MODIFY BY HAND
/// *****************************************************
/// FlutterGen
/// *****************************************************
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: deprecated_member_use,directives_ordering,implicit_dynamic_list_literal,unnecessary_import
class FontFamily {
FontFamily._();
/// Font family: Roboto
static const String roboto = 'Roboto';
}
+1 -93
View File
@@ -39,42 +39,12 @@ class TendersViewModel with ChangeNotifier {
static const int _pageSize = 10;
// --- Offset pagination state ---
// --- Infinite-scroll offset state ---
int _currentOffset = 0;
int _lastValidPages = 1;
int _lastValidLimit = _pageSize;
bool _hasMoreData = true;
bool get hasMoreData => _hasMoreData;
/// 0-based current page index.
int get currentPageIndex {
final meta = _tendersResponse?.meta;
final limit = meta?.limit ?? _lastValidLimit;
if (limit > 0) {
return _currentOffset ~/ limit;
}
if (meta?.offset != null && limit > 0) {
return meta!.offset! ~/ limit;
}
if (meta?.page != null) {
return meta!.page! - 1;
}
return 0;
}
/// 1-based current page (for display).
int get currentPage => currentPageIndex + 1;
int get totalPages {
final pages = _tendersResponse?.meta?.pages;
if (pages != null && pages > 0) {
return pages;
}
return _lastValidPages;
}
// Filter States
DateTimeRange? selectedDateRange;
String selectedStatus = 'All';
@@ -233,12 +203,6 @@ class TendersViewModel with ChangeNotifier {
case Ok<TendersResponse>():
_tendersResponse = result.value;
final meta = _tendersResponse?.meta;
if (meta?.pages != null && meta!.pages! > 0) {
_lastValidPages = meta.pages!;
}
if (meta?.limit != null && meta!.limit! > 0) {
_lastValidLimit = meta.limit!;
}
if (meta?.offset != null) {
_currentOffset = meta!.offset!;
}
@@ -299,12 +263,6 @@ class TendersViewModel with ChangeNotifier {
data: result.value.data?.copyWith(tenders: combined),
);
final newMeta = result.value.meta;
if (newMeta?.pages != null && newMeta!.pages! > 0) {
_lastValidPages = newMeta.pages!;
}
if (newMeta?.limit != null && newMeta!.limit! > 0) {
_lastValidLimit = newMeta.limit!;
}
_hasMoreData =
newMeta?.hasMore ?? (newTenders.length >= _pageSize);
} else {
@@ -492,54 +450,4 @@ class TendersViewModel with ChangeNotifier {
sortOrder = '';
notifyListeners();
}
/// [selected] is the absolute 0-based target page index.
Future<void> handlePaginationChange(int selected) async {
if (selected < 0) {
return;
}
final maxPage = totalPages - 1;
if (maxPage >= 0 && selected > maxPage) {
return;
}
if (selected == currentPageIndex) {
return;
}
await _goToOffsetPage(selected);
}
Future<void> _goToOffsetPage(int pageIndex) async {
final limit = _tendersResponse?.meta?.limit ?? _pageSize;
_currentOffset = pageIndex * limit;
await _fetchPage();
}
Future<void> _fetchPage() async {
_isLoading = true;
notifyListeners();
final result = await _tendersRepository.getTenders(
limit: _pageSize,
offset: _currentOffset,
query: _searchQuery,
sortBy: sortBy,
sortOrder: sortOrder,
publicationDateFrom: startDateUnix,
publicationDateTo: endDateUnix,
);
_handleListResult(result, fetchAllFeedbacks: true);
_isLoading = false;
notifyListeners();
_errorMessage = null;
notifyListeners();
}
/// Kept for backwards compatibility with existing callers.
/// Accepts a 1-based page number.
void jumpToPage(int page) {
handlePaginationChange(page - 1);
}
}
@@ -4,7 +4,9 @@ import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/view_models/tender_detail_view_model.dart';
import 'package:tm_app/views/detail/widgets/tender_detail_action.dart';
import 'package:tm_app/views/detail/widgets/tender_detail_card.dart';
// AI-NOTE (for PR reviewer): Import kept (commented) — the TenderDetailCard
// (static "profile match / incomplete resume" card) is disabled, not removed.
// import 'package:tm_app/views/detail/widgets/tender_detail_card.dart';
import 'package:tm_app/views/detail/widgets/tender_detail_header.dart';
import '../../../core/constants/tender_approval_status.dart';
@@ -121,8 +123,10 @@ class _TenderDetailDesktopPageState extends State<TenderDetailDesktopPage> {
detail: detail,
),
SizedBox(height: 32.0.h()),
TenderDetailCard(detail: detail),
SizedBox(height: 24.0.h()),
// AI-NOTE (for PR reviewer): Static mock card
// disabled (kept for future) — hard-coded data.
// TenderDetailCard(detail: detail),
// SizedBox(height: 24.0.h()),
TenderDetailActions(
isScreenBig: true,
detail: detail,
@@ -5,7 +5,9 @@ import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/view_models/tender_detail_view_model.dart';
import 'package:tm_app/views/detail/strings/tender_details_strings.dart';
import 'package:tm_app/views/detail/widgets/tender_detail_action.dart';
import 'package:tm_app/views/detail/widgets/tender_detail_card.dart';
// AI-NOTE (for PR reviewer): Import kept (commented) — the TenderDetailCard
// (static "profile match / incomplete resume" card) is disabled, not removed.
// import 'package:tm_app/views/detail/widgets/tender_detail_card.dart';
import 'package:tm_app/views/detail/widgets/tender_detail_header.dart';
import 'package:tm_app/views/shared/tender_app_bar.dart';
@@ -112,8 +114,10 @@ class _TenderDetailMobilePageState extends State<TenderDetailMobilePage> {
children: [
TenderDetailHeader(isScreenBig: false, detail: detail),
SizedBox(height: 24.0.h()),
TenderDetailCard(detail: detail),
SizedBox(height: 24.0.h()),
// AI-NOTE (for PR reviewer): Static mock card disabled (kept
// for future use) — shows hard-coded profile-match data.
// TenderDetailCard(detail: detail),
// SizedBox(height: 24.0.h()),
TenderDetailActions(
isScreenBig: false,
detail: detail,
@@ -4,7 +4,9 @@ import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/view_models/tender_detail_view_model.dart';
import 'package:tm_app/views/detail/widgets/tender_detail_action.dart';
import 'package:tm_app/views/detail/widgets/tender_detail_card.dart';
// AI-NOTE (for PR reviewer): Import kept (commented) — the TenderDetailCard
// (static "profile match / incomplete resume" card) is disabled, not removed.
// import 'package:tm_app/views/detail/widgets/tender_detail_card.dart';
import 'package:tm_app/views/detail/widgets/tender_detail_header.dart';
import 'package:tm_app/views/shared/tablet_navigation_widget.dart';
@@ -123,8 +125,10 @@ class _TenderDetailTabletPageState extends State<TenderDetailTabletPage> {
SizedBox(height: 28.0.h()),
TenderDetailHeader(isScreenBig: true, detail: detail),
SizedBox(height: 28.0.h()),
TenderDetailCard(detail: detail),
SizedBox(height: 24.0.h()),
// AI-NOTE (for PR reviewer): Static mock card disabled
// (kept for future) — hard-coded profile-match data.
// TenderDetailCard(detail: detail),
// SizedBox(height: 24.0.h()),
TenderDetailActions(
isScreenBig: true,
detail: detail,
@@ -42,4 +42,33 @@ class TenderDetailsStrings {
static const String requestSuccessfullyRegistered =
'Your request has been successfully registered.';
static const String confirmAndClose = 'Confirm and close';
// AI-NOTE (for PR reviewer): Formal toast copy shown after the user confirms
// the Accept/submission modal (see tender_detail_action.dart).
static const String submissionReceived =
'Thank you. We have received your submission and will be in touch with you shortly.';
// AI-NOTE (for PR reviewer): Field labels added for the rewritten
// tender-details info section (one per backend field rendered there).
// Tender detail field labels
static const String noticeTypeCode = 'Notice Type';
static const String formType = 'Form Type';
static const String noticeLanguageCode = 'Notice Language';
static const String issueDate = 'Issue Date';
static const String issueTime = 'Issue Time';
static const String procedureCode = 'Procedure';
static const String durationLabel = 'Duration';
static const String publicationDate = 'Publication Date';
static const String tenderDeadlineDate = 'Tender Deadline';
static const String submissionDeadline = 'Submission Deadline';
static const String applicationDeadline = 'Application Deadline';
static const String countryCode = 'Country';
static const String buyerName = 'Buyer';
static const String companyId = 'Company ID';
static const String buyerCity = 'City';
static const String buyerCountry = 'Buyer Country';
static const String officialLanguages = 'Official Languages';
static const String status = 'Status';
static const String tenderId = 'Tender ID';
static const String lots = 'Lots';
static const String lotId = 'Lot ID';
}
@@ -1,3 +1,7 @@
// AI-NOTE (for PR reviewer): This widget is currently unused — it was only used
// by the static mock sections in TenderDetailHeader, which are now commented
// out. It is intentionally KEPT for future use, not deleted. Safe to ignore as
// dead code for now.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:tm_app/core/utils/size_config.dart';
@@ -4,14 +4,19 @@ import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/view_models/auth_view_model.dart';
import 'package:tm_app/view_models/tender_detail_view_model.dart';
import 'package:tm_app/views/detail/widgets/meeting_time_bottom_sheet.dart';
import 'package:tm_app/views/detail/widgets/meeting_time_dialog.dart';
// AI-NOTE (for PR reviewer): These three imports are kept (commented) because
// the post-confirm navigation flow they supported (documents-completion routes
// + meeting-time modals) is preserved as commented-out code below, not deleted.
// The Accept flow now just shows a confirmation toast instead. Re-enable these
// imports if/when the navigation flow is reinstated.
// import 'package:tm_app/views/detail/widgets/meeting_time_bottom_sheet.dart';
// import 'package:tm_app/views/detail/widgets/meeting_time_dialog.dart';
import 'package:tm_app/views/shared/base_button.dart';
import 'package:tm_app/views/shared/select_submission_bottom_sheet.dart';
import '../../../core/constants/tender_approval_status.dart';
import '../../../core/constants/tender_submision_mode.dart';
import '../../../core/routes/app_routes.dart';
// import '../../../core/routes/app_routes.dart';
import '../../../core/utils/app_toast.dart';
import '../../../data/services/model/tender_data/tender_data.dart';
import '../../shared/select_submission_dialog.dart';
@@ -68,38 +73,37 @@ class _TenderDetailActionsState extends State<TenderDetailActions> {
builder: (context) {
return SelectSubmissionBottomSheet(
onConfirm: (String value) {
if (value ==
TenderSubmissionMode.selfApply.value) {
// viewModel.submitTenderApproval(
// AI-NOTE (for PR reviewer): New behavior —
// confirming the submission modal now just
// shows a confirmation toast (no API call, no
// navigation), per product request. The prior
// navigation flow is preserved below for future
// use and is intentionally NOT deleted.
_showSubmissionReceivedToast();
// if (value ==
// TenderSubmissionMode.selfApply.value) {
// // viewModel.submitTenderApproval(
// // tenderId: widget.detail.id!,
// // submissionMode: value,
// // );
// CompletionOfDocumentsRouteData(
// tenderId: widget.detail.id!,
// submissionMode: value,
// ).push(context);
// } else if (value ==
// TenderSubmissionMode.partnership.value) {
// Future.delayed(Duration.zero, () {
// if (context.mounted) {
// showModalBottomSheet(
// context: context,
// builder: (_) {
// return MeetingTimeBottomSheet(
// onConfirm: (value) {},
// );
CompletionOfDocumentsRouteData(
tenderId: widget.detail.id!,
).push(context);
} else if (value ==
TenderSubmissionMode.partnership.value) {
Future.delayed(Duration.zero, () {
if (context.mounted) {
showModalBottomSheet(
context: context,
builder: (_) {
return MeetingTimeBottomSheet(
onConfirm: (value) {
// AppToast.success(
// context,
// TenderDetailsStrings
// .meetingTimeSuccessfullySet,
// },
// );
},
);
},
);
}
});
}
// }
// });
// }
},
);
},
@@ -143,6 +147,17 @@ class _TenderDetailActionsState extends State<TenderDetailActions> {
);
}
// AI-NOTE (for PR reviewer): New helper added for the Accept flow. Shows a
// success toast confirming the submission was received. Guards on `mounted`
// because it runs from a modal callback after the sheet/dialog is popped.
// Uses the State's own context (stable page context), not the popped modal's.
void _showSubmissionReceivedToast() {
if (!mounted) {
return;
}
AppToast.success(context, TenderDetailsStrings.submissionReceived);
}
Row _webActions(Role userRole) {
return Row(
children: [
@@ -206,33 +221,37 @@ class _TenderDetailActionsState extends State<TenderDetailActions> {
builder: (context) {
return SelectSubmissionDialog(
onConfirm: (String value) {
if (value ==
TenderSubmissionMode.selfApply.value) {
if (context.mounted) {
if (userRole == Role.analyst) {
const FinalCompletionOfDocumentsRouteData()
.push(context);
} else {
CompletionOfDocumentsRouteData(
tenderId: widget.detail.id!,
).push(context);
}
}
} else if (value ==
TenderSubmissionMode.partnership.value) {
Future.delayed(Duration.zero, () {
if (context.mounted) {
showDialog(
context: context,
builder: (context) {
return MeetingTimeDialog(
onConfirm: (value) {},
);
},
);
}
});
}
// AI-NOTE (for PR reviewer): New behavior —
// toast only (no API/navigation). Old role-aware
// navigation preserved below for future use.
_showSubmissionReceivedToast();
// if (value ==
// TenderSubmissionMode.selfApply.value) {
// if (context.mounted) {
// if (userRole == Role.analyst) {
// const FinalCompletionOfDocumentsRouteData()
// .push(context);
// } else {
// CompletionOfDocumentsRouteData(
// tenderId: widget.detail.id!,
// ).push(context);
// }
// }
// } else if (value ==
// TenderSubmissionMode.partnership.value) {
// Future.delayed(Duration.zero, () {
// if (context.mounted) {
// showDialog(
// context: context,
// builder: (context) {
// return MeetingTimeDialog(
// onConfirm: (value) {},
// );
// },
// );
// }
// });
// }
},
);
},
@@ -242,41 +261,34 @@ class _TenderDetailActionsState extends State<TenderDetailActions> {
builder: (context) {
return SelectSubmissionBottomSheet(
onConfirm: (String value) {
if (value ==
TenderSubmissionMode.selfApply.value) {
// viewModel.submitTenderApproval(
// tenderId: widget.detail.id!,
// submissionMode: value,
// );
if (context.mounted) {
const FinalCompletionOfDocumentsRouteData()
.push(context);
}
} else if (value ==
TenderSubmissionMode.partnership.value) {
showDialog(
context: context,
builder: (context) {
return MeetingTimeBottomSheet(
onConfirm: (value) {
AppToast.success(
context,
TenderDetailsStrings
.meetingTimeSuccessfullySet,
);
},
);
// return MeetingTimeDialog(
// onConfirm: (String value) {
// AI-NOTE (for PR reviewer): New behavior —
// toast only (no API/navigation). Old flow
// (final-completion route + meeting-time
// bottom sheet) preserved below for future use.
_showSubmissionReceivedToast();
// if (value ==
// TenderSubmissionMode.selfApply.value) {
// if (context.mounted) {
// const CompletionOfDocumentsRouteData()
// const FinalCompletionOfDocumentsRouteData()
// .push(context);
// }
// } else if (value ==
// TenderSubmissionMode.partnership.value) {
// showDialog(
// context: context,
// builder: (context) {
// return MeetingTimeBottomSheet(
// onConfirm: (value) {
// AppToast.success(
// context,
// TenderDetailsStrings
// .meetingTimeSuccessfullySet,
// );
// },
// );
},
);
}
// },
// );
// }
},
);
},
@@ -1,3 +1,7 @@
// AI-NOTE (for PR reviewer): This widget is currently unused — its usages on the
// tender-details pages are commented out (it renders a static/mock "profile
// match 75% + incomplete resume" card, not backend data). It is intentionally
// KEPT for future use, not deleted. Safe to ignore as dead code for now.
import 'package:flutter/material.dart';
import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart';
@@ -9,7 +9,12 @@ import 'package:tm_app/views/detail/widgets/tender_detail_info_section.dart';
import '../../../core/constants/assets.dart';
import '../../shared/flag.dart';
import '../strings/tender_details_strings.dart';
import 'detail_drop_down.dart';
// AI-NOTE (for PR reviewer): Import is intentionally kept but commented out.
// The static/mock sections it supports (key risks, winning chance, project id,
// service/requirements/background dropdowns) were disabled — NOT removed —
// because they were hard-coded placeholders, not backend data. Re-enable this
// import together with those sections once the API provides that data.
// import 'detail_drop_down.dart';
class TenderDetailHeader extends StatelessWidget {
final bool isScreenBig;
@@ -41,44 +46,32 @@ class TenderDetailHeader extends StatelessWidget {
SizedBox(height: 16.0.h()),
_locationBudgetRow(),
SizedBox(height: 24.0.h()),
_projectId(),
SizedBox(height: 12.0.h()),
_winningChanceAndDificulty(),
SizedBox(height: 24.0.h()),
_keyRisksDropDown(),
SizedBox(height: 24.0.h()),
// AI-NOTE (for PR reviewer): The following mock sections are disabled
// on purpose (kept for future use). They render hard-coded values, not
// backend data, so they are commented out rather than deleted.
// _projectId(),
// SizedBox(height: 12.0.h()),
// _winningChanceAndDificulty(),
// SizedBox(height: 24.0.h()),
// _keyRisksDropDown(),
// SizedBox(height: 24.0.h()),
Divider(color: AppColors.grey20),
TenderDetailInfoSection(isScreenBig: isScreenBig, detail: detail),
SizedBox(height: 24.0.h()),
_overViewTitle(),
SizedBox(height: 12.0.h()),
_descriptionText(),
SizedBox(height: 24.0.h()),
_serviceDropDown(),
SizedBox(height: 24.0.h()),
_requirementsDropDown(),
SizedBox(height: 24.0.h()),
_backgroundDropDown(),
// AI-NOTE (for PR reviewer): Mock dropdowns disabled (kept for future).
// _serviceDropDown(),
// SizedBox(height: 24.0.h()),
// _requirementsDropDown(),
// SizedBox(height: 24.0.h()),
// _backgroundDropDown(),
],
),
);
}
Widget _serviceDropDown() =>
const DetailDropDown(title: 'Servive/Product', items: []);
Widget _backgroundDropDown() =>
const DetailDropDown(title: 'Background', items: []);
Widget _requirementsDropDown() {
return const DetailDropDown(
title: 'Requirements (Summary)',
items: [
'Market consultation for selecting and procuring a Customer Data Platform (CDP) to unify student data from multiple sources and improve personalization and marketing.',
],
);
}
Widget _descriptionText() {
return Text(
detail.description ?? '',
@@ -101,6 +94,26 @@ class TenderDetailHeader extends StatelessWidget {
);
}
// AI-NOTE (for PR reviewer): The mock widget builders below are intentionally
// preserved as commented-out code. They produce static placeholder content
// (not backend-driven) and will be reinstated once the API supplies the
// corresponding fields. Do not flag them as dead code — keep as-is.
/*
Widget _serviceDropDown() =>
const DetailDropDown(title: 'Servive/Product', items: []);
Widget _backgroundDropDown() =>
const DetailDropDown(title: 'Background', items: []);
Widget _requirementsDropDown() {
return const DetailDropDown(
title: 'Requirements (Summary)',
items: [
'Market consultation for selecting and procuring a Customer Data Platform (CDP) to unify student data from multiple sources and improve personalization and marketing.',
],
);
}
Widget _keyRisksDropDown() {
return const DetailDropDown(
title: 'Key risks & dealbreakers',
@@ -146,7 +159,7 @@ class TenderDetailHeader extends StatelessWidget {
borderRadius: BorderRadius.circular(8),
),
child: Text(
'Difficulty 2/5',
'Difficulty 2/5',
style: TextStyle(
color: AppColors.blue10,
fontSize: 12.0.sp(),
@@ -167,7 +180,7 @@ class TenderDetailHeader extends StatelessWidget {
borderRadius: BorderRadius.circular(8),
),
child: Text(
'Project ID 6243372025NLD',
'Project ID 6243372025NLD',
style: TextStyle(
color: AppColors.blue10,
fontSize: 12.0.sp(),
@@ -176,6 +189,7 @@ class TenderDetailHeader extends StatelessWidget {
),
);
}
*/
Widget _locationBudgetRow() {
return Row(
@@ -1,10 +1,21 @@
import 'package:flutter/material.dart';
import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/date_utils.dart';
import 'package:tm_app/core/utils/price_extension.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/detail/strings/tender_details_strings.dart';
import 'package:tm_app/views/detail/widgets/deadline_item.dart';
import 'package:tm_app/views/detail/widgets/info_item.dart';
// AI-NOTE (for PR reviewer): This widget was rewritten to render ONLY the
// backend-driven fields requested for the tender-details page (notice type,
// form type, languages, issue/publication dates, procedure, value, duration,
// deadlines, buyer org/address, lots, status, ids). Design is unchanged — it
// still uses InfoItem/DeadlineItem. The _infoItem() helper returns an empty
// list when a value is null/blank so empty fields don't render as blank rows
// (this is why each entry is spread with `...`). The old hard-coded
// "Reference Number" row was dropped because it isn't in the requested set.
class TenderDetailInfoSection extends StatelessWidget {
final bool isScreenBig;
final TenderData detail;
@@ -17,24 +28,114 @@ class TenderDetailInfoSection extends StatelessWidget {
@override
Widget build(BuildContext context) {
final buyer = detail.buyerOrganization;
final address = buyer?.address;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DeadlineItem(detail: detail, isScreenBig: isScreenBig),
InfoItem(
title: TenderDetailsStrings.tenderClientLabel,
value: detail.buyerOrganization!.name ?? '',
..._infoItem(TenderDetailsStrings.noticeTypeCode, detail.noticeTypeCode),
..._infoItem(TenderDetailsStrings.formType, detail.formType),
..._infoItem(
TenderDetailsStrings.noticeLanguageCode,
detail.noticeLanguageCode,
),
InfoItem(
title: TenderDetailsStrings.estimatedValue,
value:
'${detail.estimatedValue?.formattedPrice} ${detail.currency ?? ''}',
..._infoItem(
TenderDetailsStrings.issueDate,
_date(detail.issueDate),
),
InfoItem(
title: TenderDetailsStrings.tenderReferenceNumberLabel,
value: detail.noticePublicationId ?? '',
..._infoItem(
TenderDetailsStrings.issueTime,
_dateTime(detail.issueTime),
),
..._infoItem(TenderDetailsStrings.procedureCode, detail.procedureCode),
..._infoItem(
TenderDetailsStrings.estimatedValue,
detail.estimatedValue == null
? null
: '${detail.estimatedValue?.formattedPrice} ${detail.currency ?? ''}',
),
..._infoItem(TenderDetailsStrings.durationLabel, _duration()),
..._infoItem(
TenderDetailsStrings.publicationDate,
_date(detail.publicationDate),
),
..._infoItem(
TenderDetailsStrings.tenderDeadlineDate,
_date(detail.tenderDeadline),
),
..._infoItem(TenderDetailsStrings.countryCode, detail.countryCode),
..._infoItem(TenderDetailsStrings.buyerName, buyer?.name),
..._infoItem(TenderDetailsStrings.companyId, buyer?.companyId),
..._infoItem(TenderDetailsStrings.buyerCity, address?.cityName),
..._infoItem(TenderDetailsStrings.buyerCountry, address?.countryCode),
..._infoItem(
TenderDetailsStrings.officialLanguages,
(detail.officialLanguages == null ||
detail.officialLanguages!.isEmpty)
? null
: detail.officialLanguages!.join(', '),
),
..._infoItem(TenderDetailsStrings.status, detail.status),
..._infoItem(TenderDetailsStrings.tenderId, detail.tenderId),
..._lotsSection(),
],
);
}
/// Returns an [InfoItem] only when [value] has content, so empty fields are
/// not rendered as blank rows.
List<Widget> _infoItem(String title, String? value) {
if (value == null || value.trim().isEmpty) {
return const [];
}
return [InfoItem(title: title, value: value)];
}
List<Widget> _lotsSection() {
final lots = detail.lots;
if (lots == null || lots.isEmpty) {
return const [];
}
return [
SizedBox(height: 8.0.h()),
Text(
TenderDetailsStrings.lots,
style: TextStyle(
color: AppColors.grey80,
fontWeight: FontWeight.w600,
fontSize: 16.0.sp(),
),
),
SizedBox(height: 8.0.h()),
for (final lot in lots) ...[
..._infoItem(TenderDetailsStrings.lotId, lot.lotId),
..._infoItem('Title', lot.title),
..._infoItem('Description', lot.description),
],
];
}
String? _duration() {
final duration = detail.duration?.trim() ?? '';
final unit = detail.durationUnit?.trim() ?? '';
final combined = '$duration $unit'.trim();
return combined.isEmpty ? null : combined;
}
String? _date(int? unix) {
if (unix == null) {
return null;
}
return timeConvertor(unix);
}
String? _dateTime(int? unix) {
if (unix == null) {
return null;
}
return timeConvertor(unix, hasTime: true);
}
}
+24 -19
View File
@@ -87,25 +87,30 @@ class DesktopNavigationWidget extends StatelessWidget {
iconPath: AssetsManager.tenders,
activeIconPath: AssetsManager.tendersActive,
),
const SizedBox(width: 24),
_navigationItem(
context: context,
text: TendersStrings.board,
isActive: currentIndex == 2,
onTap: () {
if (currentIndex == 2) {
return;
} else {
Router.neglect(
context,
() => const BoardRouteData().go(context),
);
// context.read<TendersViewModel>().getTenders();
}
},
iconPath: AssetsManager.board,
activeIconPath: AssetsManager.boardActive,
),
// AI-NOTE (for PR reviewer): Board tab disabled (commented, not
// removed) to hide the Board page from desktop navigation per request.
// The Board route/page itself is left intact; re-enable this nav item
// to bring the tab back. Note the tab indices of later items are
// unchanged because this block is fully commented out.
// const SizedBox(width: 24),
// _navigationItem(
// context: context,
// text: TendersStrings.board,
// isActive: currentIndex == 2,
// onTap: () {
// if (currentIndex == 2) {
// return;
// } else {
// Router.neglect(
// context,
// () => const BoardRouteData().go(context),
// );
// // context.read<TendersViewModel>().getTenders();
// }
// },
// iconPath: AssetsManager.board,
// activeIconPath: AssetsManager.boardActive,
// ),
const SizedBox(width: 24),
_navigationItem(
context: context,
+1 -17
View File
@@ -13,7 +13,6 @@ import 'package:tm_app/views/tenders/strings/tenders_strings.dart';
import 'package:tm_app/views/tenders/widgets/main_tenders_slider.dart';
import 'package:tm_app/views/tenders/widgets/tenders_filter_dialog.dart';
import 'package:tm_app/views/tenders/widgets/tenders_sort_dialog.dart';
import 'package:tm_app/views/tenders/widgets/windowed_pagination.dart';
import '../../../core/constants/common_strings.dart';
import '../../../core/utils/app_toast.dart';
@@ -78,13 +77,9 @@ class _DesktopTendersPageState extends State<DesktopTendersPage> {
return const Center(child: Text(CommonStrings.noData));
}
return Column(
children: [
Expanded(
child: SingleChildScrollView(
return Center(
child: SizedBox(
width: 740,
height: 800,
child: Padding(
padding: EdgeInsets.symmetric(vertical: 4.0.h()),
child: MainTendersSlider(
@@ -93,17 +88,6 @@ class _DesktopTendersPageState extends State<DesktopTendersPage> {
),
),
),
),
),
SizedBox(
width: 740,
child: WindowedPagination(
currentPage: vm.currentPageIndex,
totalPages: vm.totalPages,
onPageChanged: vm.handlePaginationChange,
),
),
],
);
},
),
+2 -19
View File
@@ -9,7 +9,6 @@ import '../../../core/utils/app_toast.dart';
import '../../shared/tender_app_bar.dart';
import '../strings/tenders_strings.dart';
import '../widgets/main_tenders_slider.dart';
import '../widgets/windowed_pagination.dart';
class MobileTendersPage extends StatefulWidget {
const MobileTendersPage({super.key});
@@ -62,29 +61,13 @@ class _MobileTendersPageState extends State<MobileTendersPage> {
return const Center(child: Text(CommonStrings.noData));
}
return Column(
children: [
Expanded(
child: SingleChildScrollView(
return SingleChildScrollView(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 18.0.h()),
child: MainTendersSlider(
key: ValueKey(
'tenders-slider-${viewModel.currentPageIndex}',
),
tenders:
viewModel.tendersResponse?.data?.tenders ?? [],
tenders: viewModel.tendersResponse?.data?.tenders ?? [],
),
),
),
),
WindowedPagination(
currentPage: viewModel.currentPageIndex,
totalPages: viewModel.totalPages,
onPageChanged: viewModel.handlePaginationChange,
compact: true,
),
],
);
},
),
+1 -16
View File
@@ -5,7 +5,6 @@ import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/view_models/tenders_view_model.dart';
import 'package:tm_app/views/shared/tablet_navigation_widget.dart';
import 'package:tm_app/views/tenders/widgets/main_tenders_slider.dart';
import 'package:tm_app/views/tenders/widgets/windowed_pagination.dart';
import '../../../core/constants/common_strings.dart';
import '../../shared/tender_app_bar.dart';
@@ -54,30 +53,16 @@ class _TabletTendersPageState extends State<TabletTendersPage> {
viewModel.isLoading == false) {
return const Center(child: Text(CommonStrings.noData));
}
return Column(
children: [
Expanded(
child: SingleChildScrollView(
return SingleChildScrollView(
child: Padding(
padding: EdgeInsetsDirectional.only(
end: 24.0.w(),
top: 128.0.h(),
),
child: MainTendersSlider(
key: ValueKey(
'tenders-slider-${viewModel.currentPageIndex}',
),
tenders: viewModel.tendersResponse?.data?.tenders ?? [],
),
),
),
),
WindowedPagination(
currentPage: viewModel.currentPageIndex,
totalPages: viewModel.totalPages,
onPageChanged: viewModel.handlePaginationChange,
),
],
);
},
),
@@ -25,8 +25,41 @@ class MainTendersSlider extends StatefulWidget {
class _MainTendersSliderState extends State<MainTendersSlider> {
final PageController pageController = PageController(initialPage: 0);
final ScrollController _scrollController = ScrollController();
int currentPage = 1;
/// Start prefetching the next page once this fraction of the currently
/// loaded items has been scrolled/swiped past.
static const double _prefetchFraction = 0.5;
@override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
}
void _onScroll() {
if (!_scrollController.hasClients) {
return;
}
final position = _scrollController.position;
if (position.maxScrollExtent <= 0) {
return;
}
if (position.pixels >= position.maxScrollExtent * _prefetchFraction) {
_maybeLoadMore();
}
}
void _maybeLoadMore() {
final viewModel = context.read<TendersViewModel>();
if (viewModel.hasMoreData &&
!viewModel.isLoadingMore &&
!viewModel.isLoading) {
viewModel.loadMoreTenders();
}
}
void _goToPreviousPage() {
if (currentPage == 1) {
return;
@@ -82,6 +115,7 @@ class _MainTendersSliderState extends State<MainTendersSlider> {
@override
void dispose() {
pageController.dispose();
_scrollController.dispose();
super.dispose();
}
@@ -115,6 +149,12 @@ class _MainTendersSliderState extends State<MainTendersSlider> {
if (!viewModel.hasFeedbackForTender(id)) {
viewModel.getTenderFeedback(id);
}
// Infinite scroll: once we pass the halfway point of the loaded
// tenders, prefetch the next page in the background.
if (index >= (widget.tenders.length * _prefetchFraction).floor()) {
_maybeLoadMore();
}
},
itemBuilder: (context, index) {
final tender = widget.tenders[index];
@@ -230,6 +270,7 @@ class _MainTendersSliderState extends State<MainTendersSlider> {
children: [
Expanded(
child: ListView.separated(
controller: _scrollController,
padding: EdgeInsets.all(16.0.h()),
itemCount:
widget.tenders.length +
+22 -8
View File
@@ -8,7 +8,11 @@ import '../../../core/constants/assets.dart';
import '../../../core/theme/colors.dart';
import '../../../core/utils/date_utils.dart';
import '../../../core/utils/size_config.dart';
import '../../detail/strings/tender_details_strings.dart';
// AI-NOTE (for PR reviewer): Import kept (commented) because the match-percentage
// section it feeds (TenderDetailsStrings.tenderMatchProfile) is disabled, not
// removed. Re-enable alongside _matchPercentage()/_progressBar() below when the
// "match with profile" value comes from the backend.
// import '../../detail/strings/tender_details_strings.dart';
import '../../shared/flag.dart';
import '../strings/tenders_strings.dart';
import 'tender_action_buttons_row.dart';
@@ -83,13 +87,17 @@ class TenderCard extends StatelessWidget {
),
child: Column(
children: [
// Match percentage
_matchPercentage(),
SizedBox(height: isDesktop ? 4.0.h() : 8.0.h()),
// Progress bar
_progressBar(),
SizedBox(height: isDesktop ? 8.0.h() : 16.0.h()),
// AI-NOTE (for PR reviewer): The "Match with your profile"
// label, the 45% value and the progress bar are disabled on
// purpose (kept for future use). They show a hard-coded 45%,
// not a backend value, so they are commented out, not deleted.
// // Match percentage
// _matchPercentage(),
// SizedBox(height: isDesktop ? 4.0.h() : 8.0.h()),
//
// // Progress bar
// _progressBar(),
// SizedBox(height: isDesktop ? 8.0.h() : 16.0.h()),
// Location and apply button
Row(
@@ -243,6 +251,11 @@ class TenderCard extends StatelessWidget {
);
}
// AI-NOTE (for PR reviewer): _matchPercentage() and _progressBar() are
// preserved as commented-out code. Both render a hard-coded 45% placeholder
// (no backend source yet), so they are disabled rather than removed. Keep them
// for when the profile-match score becomes available from the API.
/*
Widget _matchPercentage() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -287,6 +300,7 @@ class TenderCard extends StatelessWidget {
),
);
}
*/
Widget _locationInfo() {
return Expanded(
@@ -1,250 +0,0 @@
import 'package:flutter/material.dart';
import 'package:tm_app/core/theme/colors.dart';
/// A windowed numbered pagination widget that mirrors the Next.js
/// implementation: ±10 page-button window around the current page,
/// 1-based labels, prev/next chevrons.
class WindowedPagination extends StatelessWidget {
const WindowedPagination({
super.key,
required this.currentPage,
required this.totalPages,
required this.onPageChanged,
this.maxPageJump = 10,
this.compact = false,
});
/// 0-based absolute current page index.
final int currentPage;
/// Total number of pages (1-based count).
final int totalPages;
/// Called with the 0-based target page index.
final ValueChanged<int> onPageChanged;
/// Half-width of the page-button window (web uses 10).
final int maxPageJump;
/// If true, renders smaller buttons (for mobile).
final bool compact;
@override
Widget build(BuildContext context) {
if (totalPages <= 1) {
return const SizedBox.shrink();
}
final clampedCurrent = currentPage.clamp(0, totalPages - 1);
final windowStart = (clampedCurrent - maxPageJump).clamp(0, totalPages - 1);
final windowEnd = (clampedCurrent + maxPageJump).clamp(0, totalPages - 1);
final buttons = <Widget>[];
buttons.add(
_NavButton(
icon: Icons.chevron_left,
enabled: clampedCurrent > 0,
compact: compact,
onTap: () => onPageChanged(clampedCurrent - 1),
),
);
if (windowStart > 0) {
buttons.add(_buildPageButton(0, clampedCurrent));
if (windowStart > 1) {
buttons.add(const _Ellipsis());
}
}
for (var i = windowStart; i <= windowEnd; i++) {
buttons.add(_buildPageButton(i, clampedCurrent));
}
if (windowEnd < totalPages - 1) {
if (windowEnd < totalPages - 2) {
buttons.add(const _Ellipsis());
}
buttons.add(_buildPageButton(totalPages - 1, clampedCurrent));
}
buttons.add(
_NavButton(
icon: Icons.chevron_right,
enabled: clampedCurrent < totalPages - 1,
compact: compact,
onTap: () => onPageChanged(clampedCurrent + 1),
),
);
final gap = compact ? 4.0 : 6.0;
return Padding(
padding: EdgeInsets.symmetric(vertical: compact ? 8 : 12),
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: ConstrainedBox(
constraints: BoxConstraints(minWidth: constraints.maxWidth),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: _withSpacing(buttons, gap),
),
),
);
},
),
);
}
Widget _buildPageButton(int pageIndex, int currentPageIndex) {
final isActive = pageIndex == currentPageIndex;
return _PageButton(
label: '${pageIndex + 1}',
isActive: isActive,
compact: compact,
onTap: isActive ? null : () => onPageChanged(pageIndex),
);
}
static List<Widget> _withSpacing(List<Widget> items, double gap) {
final result = <Widget>[];
for (var i = 0; i < items.length; i++) {
result.add(items[i]);
if (i != items.length - 1) {
result.add(SizedBox(width: gap));
}
}
return result;
}
}
class _PageButton extends StatelessWidget {
const _PageButton({
required this.label,
required this.isActive,
required this.compact,
this.onTap,
});
final String label;
final bool isActive;
final bool compact;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
final size = compact ? 32.0 : 38.0;
return SizedBox(
width: size,
height: size,
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(12),
onTap: onTap,
child: Container(
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
gradient: isActive
? const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [AppColors.mainBlue, AppColors.primaryColor],
)
: null,
color: isActive ? null : AppColors.grey0,
border: Border.all(
color: isActive ? Colors.transparent : AppColors.grey30,
width: 1,
),
boxShadow: isActive
? [
BoxShadow(
color: AppColors.mainBlue.withValues(alpha: 0.25),
blurRadius: 8,
offset: const Offset(0, 2),
),
]
: null,
),
child: Text(
label,
style: TextStyle(
fontSize: compact ? 12 : 13,
fontWeight: isActive ? FontWeight.w600 : FontWeight.w500,
color: isActive ? Colors.white : AppColors.grey70,
),
),
),
),
),
);
}
}
class _NavButton extends StatelessWidget {
const _NavButton({
required this.icon,
required this.enabled,
required this.compact,
required this.onTap,
});
final IconData icon;
final bool enabled;
final bool compact;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final size = compact ? 32.0 : 38.0;
return SizedBox(
width: size,
height: size,
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(12),
onTap: enabled ? onTap : null,
child: Container(
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: AppColors.grey0,
border: Border.all(
color: enabled ? AppColors.grey30 : AppColors.grey20,
width: 1,
),
),
child: Icon(
icon,
size: compact ? 18 : 20,
color: enabled ? AppColors.grey70 : AppColors.grey40,
),
),
),
),
);
}
}
class _Ellipsis extends StatelessWidget {
const _Ellipsis();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 2),
child: Text(
'',
style: TextStyle(
fontSize: 14,
color: AppColors.grey60,
fontWeight: FontWeight.w500,
),
),
);
}
}
+22
View File
@@ -51,6 +51,10 @@ void main() {
expect(feedback.tender?.buyerOrganization?.name, 'Buyer Org');
});
// AI-NOTE (for PR reviewer): These TenderData/Organization literals were
// updated only to satisfy the new required fields added to those models
// (notice_type_code, form_type, issue_date/time, lots, official_languages,
// company_id, address). Test assertions/behavior are unchanged.
test('toJson should return valid JSON map', () {
final tender = const TenderData(
success: true,
@@ -58,6 +62,11 @@ void main() {
id: 'TND123',
tenderId: 'TND123',
noticePublicationId: 'NP123',
noticeTypeCode: 'cn-standard',
formType: 'competition',
noticeLanguageCode: 'ENG',
issueDate: 1700000000,
issueTime: 1700000000,
publicationDate: 1700000000,
title: 'Tender Title',
description: 'Some description',
@@ -74,7 +83,11 @@ void main() {
durationUnit: 'month',
buyerOrganization: Organization(
name: 'Test Organization',
companyId: 'ORG001',
address: null,
),
lots: null,
officialLanguages: null,
status: 'active',
);
@@ -114,6 +127,11 @@ void main() {
id: 'TND001',
tenderId: 'TND001',
noticePublicationId: 'NP1',
noticeTypeCode: 'cn-standard',
formType: 'competition',
noticeLanguageCode: 'ENG',
issueDate: 1700000000,
issueTime: 1700000000,
publicationDate: 1700000000,
title: 'Old Tender',
description: 'Old desc',
@@ -130,7 +148,11 @@ void main() {
durationUnit: 'month',
buyerOrganization: Organization(
name: 'Old Org',
companyId: 'ORG001',
address: null,
),
lots: null,
officialLanguages: null,
status: 'inactive',
),
updatedAt: 1750000000,