Update dependencies and enhance tender approval status handling

- Updated `pubspec.lock` to reflect new versions for several packages, including `async`, `fake_async`, `leak_tracker`, and `vm_service`.
- Modified `TenderApprovalStatus` enum to include a new status `approved`.
- Updated routing for `YourTendersRouteData` to accept a status parameter and pass it to the `YourTendersScreen`.
- Adjusted API calls and view models to utilize the new status handling, ensuring proper filtering and display of tender data based on approval status.
- Enhanced filter dialog to support new status options and improved user feedback in the UI for submitted and approved tenders.
This commit is contained in:
amirrezaghabeli
2025-10-12 14:57:33 +03:30
parent cccd637771
commit 6d674a4a1b
14 changed files with 321 additions and 186 deletions
@@ -3,7 +3,8 @@ enum TenderApprovalStatus {
submitted, submitted,
rejected, rejected,
unsubmitted, unsubmitted,
unrejected; unrejected,
approved;
/// Convert enum to string value for API /// Convert enum to string value for API
String get value { String get value {
@@ -16,6 +17,8 @@ enum TenderApprovalStatus {
return 'unsubmitted'; return 'unsubmitted';
case TenderApprovalStatus.unrejected: case TenderApprovalStatus.unrejected:
return 'unrejected'; return 'unrejected';
case TenderApprovalStatus.approved:
return 'approved';
} }
} }
} }
+3 -2
View File
@@ -212,11 +212,12 @@ class NotificationRouteData extends GoRouteData with _$NotificationRouteData {
@TypedGoRoute<YourTendersRouteData>(path: '/your-tenders') @TypedGoRoute<YourTendersRouteData>(path: '/your-tenders')
class YourTendersRouteData extends GoRouteData with _$YourTendersRouteData { class YourTendersRouteData extends GoRouteData with _$YourTendersRouteData {
const YourTendersRouteData(); final String status;
const YourTendersRouteData({required this.status});
@override @override
Widget build(BuildContext context, GoRouterState state) { Widget build(BuildContext context, GoRouterState state) {
return yourTendersProvider(child: const YourTendersScreen()); return yourTendersProvider(child: YourTendersScreen(status: status));
} }
} }
+7 -2
View File
@@ -247,10 +247,15 @@ RouteBase get $yourTendersRouteData => GoRouteData.$route(
mixin _$YourTendersRouteData on GoRouteData { mixin _$YourTendersRouteData on GoRouteData {
static YourTendersRouteData _fromState(GoRouterState state) => static YourTendersRouteData _fromState(GoRouterState state) =>
const YourTendersRouteData(); YourTendersRouteData(status: state.uri.queryParameters['status']!);
YourTendersRouteData get _self => this as YourTendersRouteData;
@override @override
String get location => GoRouteData.$location('/your-tenders'); String get location => GoRouteData.$location(
'/your-tenders',
queryParams: {'status': _self.status},
);
@override @override
void go(BuildContext context) => context.go(location); void go(BuildContext context) => context.go(location);
+1 -1
View File
@@ -8,7 +8,7 @@ class YourTendersApi {
required String createdTo, required String createdTo,
required String status, required String status,
}) { }) {
String url = '$tenderApproval?limit=$limit&offset=$offset&status=submitted'; String url = '$tenderApproval?limit=$limit&offset=$offset';
if (createdFrom.isNotEmpty && createdTo.isNotEmpty) { if (createdFrom.isNotEmpty && createdTo.isNotEmpty) {
url += '&created_from=$createdFrom&created_to=$createdTo'; url += '&created_from=$createdFrom&created_to=$createdTo';
+8 -3
View File
@@ -279,7 +279,7 @@ class YourTendersViewModel with ChangeNotifier {
offset: _tendersOffset, offset: _tendersOffset,
); );
await getTenders(requestModel: requestModel, reset: true); await getTenders(requestModel: requestModel, reset: true);
} else if (selectedStatus == YourTendersStrings.tendersSubmitted) { } else if (selectedStatus == TenderApprovalStatus.submitted.value) {
final requestModel = GetTendersRequestModel( final requestModel = GetTendersRequestModel(
status: TenderApprovalStatus.submitted.value, status: TenderApprovalStatus.submitted.value,
createdFrom: startDateUnix, createdFrom: startDateUnix,
@@ -288,16 +288,21 @@ class YourTendersViewModel with ChangeNotifier {
offset: _tendersOffset, offset: _tendersOffset,
); );
await getTenders(requestModel: requestModel, reset: true); await getTenders(requestModel: requestModel, reset: true);
} else if (selectedStatus == YourTendersStrings.likeTenders) { } else if (selectedStatus == TenderFeedback.liked.value) {
await getTendersFeedback( await getTendersFeedback(
feedbackType: TenderFeedback.liked.value, feedbackType: TenderFeedback.liked.value,
reset: true, reset: true,
); );
} else if (selectedStatus == YourTendersStrings.dislikeTenders) { } else if (selectedStatus == TenderFeedback.disliked.value) {
await getTendersFeedback( await getTendersFeedback(
feedbackType: TenderFeedback.disliked.value, feedbackType: TenderFeedback.disliked.value,
reset: true, reset: true,
); );
} else if (selectedStatus == TenderApprovalStatus.approved.value) {
await getTendersFeedback(
feedbackType: TenderApprovalStatus.approved.value,
reset: true,
);
} }
} }
+7 -2
View File
@@ -7,6 +7,7 @@ import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/view_models/home_view_model.dart'; import 'package:tm_app/view_models/home_view_model.dart';
import 'package:tm_app/views/shared/desktop_navigation_widget.dart'; import 'package:tm_app/views/shared/desktop_navigation_widget.dart';
import '../../../core/constants/tender_approval_status.dart';
import '../strings/home_strings.dart'; import '../strings/home_strings.dart';
import '../widgets.dart'; import '../widgets.dart';
@@ -138,7 +139,9 @@ class DesktopHomePage extends StatelessWidget {
width: 178, width: 178,
height: 148, height: 148,
onTap: () { onTap: () {
const YourTendersRouteData().push(context); YourTendersRouteData(
status: TenderApprovalStatus.submitted.value,
).push(context);
}, },
), ),
), ),
@@ -154,7 +157,9 @@ class DesktopHomePage extends StatelessWidget {
width: 178, width: 178,
height: 148, height: 148,
onTap: () { onTap: () {
const YourTendersRouteData().push(context); YourTendersRouteData(
status: TenderApprovalStatus.approved.value,
).push(context);
}, },
), ),
), ),
+7 -2
View File
@@ -8,6 +8,7 @@ import 'package:tm_app/data/services/model/tender_approvals_stats_response/tende
import 'package:tm_app/view_models/home_view_model.dart'; import 'package:tm_app/view_models/home_view_model.dart';
import 'package:tm_app/views/shared/tender_app_bar.dart'; import 'package:tm_app/views/shared/tender_app_bar.dart';
import '../../../core/constants/tender_approval_status.dart';
import '../strings/home_strings.dart'; import '../strings/home_strings.dart';
import '../widgets.dart'; import '../widgets.dart';
@@ -108,7 +109,9 @@ class MobileHomePage extends StatelessWidget {
textColor: AppColors.mainBlue, textColor: AppColors.mainBlue,
enableTap: true, enableTap: true,
onTap: () { onTap: () {
const YourTendersRouteData().push(context); YourTendersRouteData(
status: TenderApprovalStatus.submitted.value,
).push(context);
}, },
), ),
TenderCard( TenderCard(
@@ -120,7 +123,9 @@ class MobileHomePage extends StatelessWidget {
textColor: AppColors.jellyBean, textColor: AppColors.jellyBean,
enableTap: true, enableTap: true,
onTap: () { onTap: () {
const YourTendersRouteData().push(context); YourTendersRouteData(
status: TenderApprovalStatus.approved.value,
).push(context);
}, },
), ),
], ],
+7 -2
View File
@@ -8,6 +8,7 @@ import 'package:tm_app/view_models/home_view_model.dart';
import 'package:tm_app/views/shared/tablet_navigation_widget.dart'; import 'package:tm_app/views/shared/tablet_navigation_widget.dart';
import '../../../core/constants/assets.dart'; import '../../../core/constants/assets.dart';
import '../../../core/constants/tender_approval_status.dart';
import '../../shared/tender_app_bar.dart'; import '../../shared/tender_app_bar.dart';
import '../strings/home_strings.dart'; import '../strings/home_strings.dart';
import '../widgets.dart'; import '../widgets.dart';
@@ -130,7 +131,9 @@ class TabletHomePage extends StatelessWidget {
width: double.infinity, width: double.infinity,
height: 148, height: 148,
onTap: () { onTap: () {
const YourTendersRouteData().push(context); YourTendersRouteData(
status: TenderApprovalStatus.submitted.value,
).push(context);
}, },
), ),
), ),
@@ -146,7 +149,9 @@ class TabletHomePage extends StatelessWidget {
width: double.infinity, width: double.infinity,
height: 148, height: 148,
onTap: () { onTap: () {
const YourTendersRouteData().push(context); YourTendersRouteData(
status: TenderApprovalStatus.approved.value,
).push(context);
}, },
), ),
), ),
@@ -61,7 +61,7 @@ class _YourTendersMobilePageState extends State<YourTendersMobilePage> {
return Column( return Column(
children: [ children: [
if (viewModel.selectedStatus == if (viewModel.selectedStatus ==
YourTendersStrings.likeTenders && TenderFeedback.liked.value &&
viewModel.likedTendersData != null) viewModel.likedTendersData != null)
Expanded( Expanded(
child: LikedDislikedTendersList( child: LikedDislikedTendersList(
@@ -72,7 +72,7 @@ class _YourTendersMobilePageState extends State<YourTendersMobilePage> {
), ),
) )
else if (viewModel.selectedStatus == else if (viewModel.selectedStatus ==
YourTendersStrings.dislikeTenders && TenderFeedback.disliked.value &&
viewModel.likedTendersData != null) viewModel.likedTendersData != null)
Expanded( Expanded(
child: LikedDislikedTendersList( child: LikedDislikedTendersList(
@@ -7,10 +7,9 @@ import 'package:tm_app/views/your_tenders/pages/your_tenders_desktop_page.dart';
import 'package:tm_app/views/your_tenders/pages/your_tenders_mobile_page.dart'; import 'package:tm_app/views/your_tenders/pages/your_tenders_mobile_page.dart';
import 'package:tm_app/views/your_tenders/pages/your_tenders_tablet_page.dart'; import 'package:tm_app/views/your_tenders/pages/your_tenders_tablet_page.dart';
import '../../../data/services/model/request_models/get_tenders_request_model/get_tenders_request_model.dart';
class YourTendersScreen extends StatefulWidget { class YourTendersScreen extends StatefulWidget {
const YourTendersScreen({super.key}); const YourTendersScreen({required this.status, super.key});
final String status;
@override @override
State<YourTendersScreen> createState() => _YourTendersScreenState(); State<YourTendersScreen> createState() => _YourTendersScreenState();
@@ -23,8 +22,8 @@ class _YourTendersScreenState extends State<YourTendersScreen> {
super.initState(); super.initState();
viewModel = context.read<YourTendersViewModel>(); viewModel = context.read<YourTendersViewModel>();
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
final requestModel = GetTendersRequestModel(); viewModel.setStatus(widget.status);
viewModel.getTenders(requestModel: requestModel, reset: true); viewModel.callWithFilter();
}); });
} }
@@ -1,5 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/data/services/model/request_models/get_tenders_request_model/get_tenders_request_model.dart';
import 'package:tm_app/view_models/your_tenders_view_model.dart';
import 'package:tm_app/views/your_tenders/strings/your_tenders_strings.dart'; import 'package:tm_app/views/your_tenders/strings/your_tenders_strings.dart';
import 'your_tenders_filter_dialog.dart'; import 'your_tenders_filter_dialog.dart';
@@ -11,6 +14,7 @@ class FilterButton extends StatelessWidget {
const FilterButton({required this.platformType, super.key}); const FilterButton({required this.platformType, super.key});
void _openFilter(BuildContext context) { void _openFilter(BuildContext context) {
final viewModel = context.read<YourTendersViewModel>();
if (platformType == PlatformType.mobile) { if (platformType == PlatformType.mobile) {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
@@ -19,7 +23,24 @@ class FilterButton extends StatelessWidget {
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)), borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
), ),
builder: (_) => const YourTendersFilterDialog(), builder:
(_) => YourTendersFilterDialog(
viewModel: viewModel,
onFilterChanged: (dateRange, status) {
viewModel.setDateRange(dateRange);
viewModel.setStatus(status!);
if (status == YourTendersStrings.all) {
GetTendersRequestModel requestModel = GetTendersRequestModel(
status: '',
createdFrom: viewModel.startDateUnix,
createdTo: viewModel.endDateUnix,
);
viewModel.getTenders(requestModel: requestModel);
} else {
viewModel.callWithFilter();
}
},
),
); );
} else { } else {
showDialog( showDialog(
@@ -30,7 +51,24 @@ class FilterButton extends StatelessWidget {
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
), ),
child: const YourTendersFilterDialog(), child: YourTendersFilterDialog(
viewModel: viewModel,
onFilterChanged: (dateRange, status) {
viewModel.setDateRange(dateRange);
viewModel.setStatus(status!);
if (status == YourTendersStrings.all) {
GetTendersRequestModel requestModel =
GetTendersRequestModel(
status: '',
createdFrom: viewModel.startDateUnix,
createdTo: viewModel.endDateUnix,
);
viewModel.getTenders(requestModel: requestModel);
} else {
viewModel.callWithFilter();
}
},
),
), ),
); );
} }
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tm_app/core/constants/tender_approval_status.dart';
import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/view_models/your_tenders_view_model.dart'; import 'package:tm_app/view_models/your_tenders_view_model.dart';
@@ -57,12 +58,24 @@ class _TendersSubmittedState extends State<TendersSubmitted> {
return Consumer<YourTendersViewModel>( return Consumer<YourTendersViewModel>(
builder: (context, viewModel, child) { builder: (context, viewModel, child) {
if (widget.approvedTenders.data == null) { if (widget.approvedTenders.data == null) {
return const Center(child: Text('No submitted projects yet.')); return Center(
child: Text(
viewModel.selectedStatus == TenderApprovalStatus.approved.value
? 'No Approved data.'
: 'No Submitted data.',
),
);
} }
final tenders = widget.approvedTenders.data!.tenders ?? []; final tenders = widget.approvedTenders.data!.tenders ?? [];
if (tenders.isEmpty) { if (tenders.isEmpty) {
return const Center(child: Text('No submitted projects yet.')); return Center(
child: Text(
viewModel.selectedStatus == TenderApprovalStatus.approved.value
? 'No Approved data.'
: 'No Submitted data.',
),
);
} }
final itemCount = final itemCount =
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart'; import 'package:intl/intl.dart';
import 'package:tm_app/core/constants/assets.dart'; import 'package:tm_app/core/constants/assets.dart';
import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/core/utils/size_config.dart';
@@ -9,171 +9,235 @@ import 'package:tm_app/view_models/your_tenders_view_model.dart';
import 'package:tm_app/views/shared/base_button.dart'; import 'package:tm_app/views/shared/base_button.dart';
import 'package:tm_app/views/your_tenders/strings/your_tenders_strings.dart'; import 'package:tm_app/views/your_tenders/strings/your_tenders_strings.dart';
class YourTendersFilterDialog extends StatelessWidget { import '../../../core/constants/tender_approval_status.dart';
const YourTendersFilterDialog({super.key}); import '../../../core/constants/tender_feedback.dart';
Widget _dateRangePill(BuildContext context) { class YourTendersFilterDialog extends StatefulWidget {
final viewModel = context.watch<YourTendersViewModel>(); final YourTendersViewModel viewModel;
final start = viewModel.selectedDateRange?.start; final Function(DateTimeRange? dateRange, String? status) onFilterChanged;
final end = viewModel.selectedDateRange?.end; const YourTendersFilterDialog({
required this.viewModel,
required this.onFilterChanged,
super.key,
});
Future<void> pick() async { @override
final picked = await showDateRangePicker( State<YourTendersFilterDialog> createState() =>
context: context, _YourTendersFilterDialogState();
firstDate: DateTime(2020), }
lastDate: DateTime(2035),
initialDateRange: viewModel.selectedDateRange,
);
if (picked != null) {
viewModel.setDateRange(picked);
}
}
return InkWell( class _YourTendersFilterDialogState extends State<YourTendersFilterDialog> {
borderRadius: BorderRadius.circular(12), DateTimeRange? dateRange;
onTap: pick, String? status = YourTendersStrings.all;
child: Container(
height: 56.0.h(), final _fmt = DateFormat('yyyy/MM/dd');
padding: const EdgeInsets.symmetric(horizontal: 20),
decoration: BoxDecoration( String get startDateFormatted =>
color: AppColors.grey0, dateRange?.start != null ? _fmt.format(dateRange!.start) : 'YYYY/MM/DD';
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.grey40), String get endDateFormatted =>
), dateRange?.end != null ? _fmt.format(dateRange!.end) : 'YYYY/MM/DD';
child: Center(
child: Stack( String get startDateUnix =>
alignment: Alignment.center, dateRange?.start != null
children: [ ? (dateRange!.start.millisecondsSinceEpoch ~/ 1000).toString()
Align( : '';
alignment: Alignment.center,
child: Container( String get endDateUnix =>
width: 1, dateRange?.end != null
margin: const EdgeInsets.symmetric(vertical: 8), ? (dateRange!.end.millisecondsSinceEpoch ~/ 1000).toString()
color: AppColors.grey40, : '';
),
), @override
Align( void initState() {
alignment: Alignment.center, super.initState();
child: SizedBox( dateRange = widget.viewModel.selectedDateRange;
width: 26.0.w(), status = _mapApiValueToDisplayString(widget.viewModel.selectedStatus);
height: 26.0.h(), }
child: SvgPicture.asset(AssetsManager.trailingIcon),
), Future<void> pick() async {
), final picked = await showDateRangePicker(
Row( context: context,
mainAxisAlignment: MainAxisAlignment.spaceBetween, firstDate: DateTime(2000),
children: [ lastDate: DateTime(2050),
Text( initialDateRange: dateRange,
start == null
? YourTendersStrings.startDate
: viewModel.startDateFormatted,
style: TextStyle(
fontSize: 16.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey70,
),
),
Text(
end == null
? YourTendersStrings.endDate
: viewModel.endDateFormatted,
style: TextStyle(
fontSize: 16.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey70,
),
),
],
),
],
),
),
),
); );
if (picked != null) {
dateRange = picked;
setState(() {});
}
}
void setStatus(String status) {
this.status = status;
setState(() {});
}
/// Maps API values to display strings for radio buttons
String _mapApiValueToDisplayString(String apiValue) {
final mapping = {
'All': YourTendersStrings.all,
'like': YourTendersStrings.likeTenders,
'dislike': YourTendersStrings.dislikeTenders,
'approved': YourTendersStrings.approvedTenders,
'submitted': YourTendersStrings.tendersSubmitted,
};
return mapping[apiValue] ?? YourTendersStrings.all;
}
/// Maps display strings to API values for the view model
String _mapDisplayStringToApiValue(String displayString) {
final mapping = {
YourTendersStrings.all: YourTendersStrings.all,
YourTendersStrings.likeTenders: TenderFeedback.liked.value,
YourTendersStrings.dislikeTenders: TenderFeedback.disliked.value,
YourTendersStrings.approvedTenders: TenderApprovalStatus.approved.value,
YourTendersStrings.tendersSubmitted: TenderApprovalStatus.submitted.value,
};
return mapping[displayString] ?? YourTendersStrings.all;
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<YourTendersViewModel>( return SingleChildScrollView(
builder: (context, vm, child) { child: Padding(
return SingleChildScrollView( padding: const EdgeInsets.all(16),
child: Padding( child: Column(
padding: const EdgeInsets.all(16), mainAxisSize: MainAxisSize.min,
child: Column( crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, children: [
crossAxisAlignment: CrossAxisAlignment.start, Center(
children: [ child: Text(
Center( YourTendersStrings.filter,
child: Text( style: TextStyle(
YourTendersStrings.filter, fontSize: 20.0.sp(),
style: TextStyle( fontWeight: FontWeight.w700,
fontSize: 20.0.sp(), color: AppColors.grey70,
fontWeight: FontWeight.w700, ),
color: AppColors.grey70, ),
), ),
SizedBox(height: 16.0.h()),
Text(
YourTendersStrings.selectCreationRangeDate,
style: TextStyle(
fontSize: 16.0.sp(),
fontWeight: FontWeight.w600,
color: AppColors.grey80,
),
),
SizedBox(height: 20.0.h()),
InkWell(
borderRadius: BorderRadius.circular(12),
onTap: pick,
child: Container(
height: 56.0.h(),
padding: const EdgeInsets.symmetric(horizontal: 20),
decoration: BoxDecoration(
color: AppColors.grey0,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: AppColors.grey40),
),
child: Center(
child: Stack(
alignment: Alignment.center,
children: [
Align(
alignment: Alignment.center,
child: Container(
width: 1,
margin: const EdgeInsets.symmetric(vertical: 8),
color: AppColors.grey40,
),
),
Align(
alignment: Alignment.center,
child: SizedBox(
width: 26.0.w(),
height: 26.0.h(),
child: SvgPicture.asset(AssetsManager.trailingIcon),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
dateRange?.start == null
? YourTendersStrings.startDate
: startDateFormatted,
style: TextStyle(
fontSize: 16.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey70,
),
),
Text(
dateRange?.end == null
? YourTendersStrings.endDate
: endDateFormatted,
style: TextStyle(
fontSize: 16.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey70,
),
),
],
),
],
), ),
), ),
SizedBox(height: 16.0.h()), ),
Text( ),
YourTendersStrings.selectCreationRangeDate, SizedBox(height: 16.0.h()),
Text(
YourTendersStrings.status,
style: TextStyle(
fontSize: 16.0.sp(),
fontWeight: FontWeight.w600,
color: AppColors.grey70,
),
),
...[
YourTendersStrings.all,
YourTendersStrings.likeTenders,
YourTendersStrings.dislikeTenders,
YourTendersStrings.approvedTenders,
YourTendersStrings.tendersSubmitted,
].map((status) {
return RadioListTile(
value: status,
groupValue: this.status,
activeColor: AppColors.mainBlue,
onChanged: (value) => setStatus(value!),
title: Text(
status,
style: TextStyle( style: TextStyle(
fontSize: 16.0.sp(), fontSize: 16.0.sp(),
fontWeight: FontWeight.w600, fontWeight: FontWeight.w400,
color: AppColors.grey80,
),
),
SizedBox(height: 20.0.h()),
_dateRangePill(context),
SizedBox(height: 16.0.h()),
Text(
YourTendersStrings.status,
style: TextStyle(
fontSize: 16.0.sp(),
fontWeight: FontWeight.w600,
color: AppColors.grey70, color: AppColors.grey70,
), ),
), ),
...[ );
YourTendersStrings.all, }),
YourTendersStrings.likeTenders, SizedBox(height: 16.0.h()),
YourTendersStrings.dislikeTenders, SizedBox(
YourTendersStrings.approvedTenders, width: double.infinity,
YourTendersStrings.tendersSubmitted, child: BaseButton(
].map((status) { isEnabled: true,
return RadioListTile( onPressed: () {
value: status, context.pop();
groupValue: vm.selectedStatus, widget.onFilterChanged(
activeColor: AppColors.mainBlue, dateRange,
onChanged: (value) => vm.setStatus(value!), _mapDisplayStringToApiValue(status!),
title: Text(
status,
style: TextStyle(
fontSize: 16.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey70,
),
),
); );
}), },
SizedBox(height: 16.0.h()), text: YourTendersStrings.confirm,
SizedBox( textColor: AppColors.textBlue,
width: double.infinity, backgroundColor: AppColors.primary20,
child: BaseButton( ),
isEnabled: true,
onPressed: () {
context.pop();
vm.callWithFilter();
},
text: YourTendersStrings.confirm,
textColor: AppColors.textBlue,
backgroundColor: AppColors.primary20,
),
),
],
), ),
), ],
); ),
}, ),
); );
} }
} }
+3 -11
View File
@@ -1,5 +1,3 @@
////////////////////////////
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:tm_app/data/services/model/error/error_model.dart'; import 'package:tm_app/data/services/model/error/error_model.dart';
@@ -41,9 +39,7 @@ void main() {
details: 'Server error', details: 'Server error',
); );
final updated = error.copyWith( final updated = error.copyWith(message: 'Timeout error');
message: 'Timeout error',
);
expect(updated.message, 'Timeout error'); expect(updated.message, 'Timeout error');
expect(updated.code, '500'); expect(updated.code, '500');
@@ -51,11 +47,7 @@ void main() {
}); });
test('fromJson handles null fields safely', () { test('fromJson handles null fields safely', () {
final json = { final json = {'message': null, 'code': null, 'details': null};
'message': null,
'code': null,
'details': null,
};
final error = ErrorModel.fromJson(json); final error = ErrorModel.fromJson(json);
@@ -81,4 +73,4 @@ void main() {
}); });
} }
//////////////////////////// ////////////////////////////