Merge pull request 'dialogs_fix' (#212) from dialogs_fix into main

Reviewed-on: https://repo.ravanertebat.com/TM/tm_app/pulls/212
This commit is contained in:
a.ghabeli
2025-10-12 15:30:34 +03:30
17 changed files with 643 additions and 438 deletions
@@ -61,7 +61,7 @@ class _YourTendersMobilePageState extends State<YourTendersMobilePage> {
return Column(
children: [
if (viewModel.selectedStatus ==
YourTendersStrings.likeTenders &&
TenderFeedback.liked.value &&
viewModel.likedTendersData != null)
Expanded(
child: LikedDislikedTendersList(
@@ -72,7 +72,7 @@ class _YourTendersMobilePageState extends State<YourTendersMobilePage> {
),
)
else if (viewModel.selectedStatus ==
YourTendersStrings.dislikeTenders &&
TenderFeedback.disliked.value &&
viewModel.likedTendersData != null)
Expanded(
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_tablet_page.dart';
import '../../../data/services/model/request_models/get_tenders_request_model/get_tenders_request_model.dart';
class YourTendersScreen extends StatefulWidget {
const YourTendersScreen({super.key});
const YourTendersScreen({required this.status, super.key});
final String status;
@override
State<YourTendersScreen> createState() => _YourTendersScreenState();
@@ -23,8 +22,8 @@ class _YourTendersScreenState extends State<YourTendersScreen> {
super.initState();
viewModel = context.read<YourTendersViewModel>();
WidgetsBinding.instance.addPostFrameCallback((_) {
final requestModel = GetTendersRequestModel();
viewModel.getTenders(requestModel: requestModel, reset: true);
viewModel.setStatus(widget.status);
viewModel.callWithFilter();
});
}
@@ -1,5 +1,8 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.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 'your_tenders_filter_dialog.dart';
@@ -11,6 +14,7 @@ class FilterButton extends StatelessWidget {
const FilterButton({required this.platformType, super.key});
void _openFilter(BuildContext context) {
final viewModel = context.read<YourTendersViewModel>();
if (platformType == PlatformType.mobile) {
showModalBottomSheet(
context: context,
@@ -19,7 +23,24 @@ class FilterButton extends StatelessWidget {
shape: const RoundedRectangleBorder(
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 {
showDialog(
@@ -30,7 +51,24 @@ class FilterButton extends StatelessWidget {
shape: RoundedRectangleBorder(
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: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/utils/size_config.dart';
import 'package:tm_app/view_models/your_tenders_view_model.dart';
@@ -57,12 +58,24 @@ class _TendersSubmittedState extends State<TendersSubmitted> {
return Consumer<YourTendersViewModel>(
builder: (context, viewModel, child) {
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 ?? [];
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 =
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:intl/intl.dart';
import 'package:tm_app/core/constants/assets.dart';
import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart';
@@ -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/your_tenders/strings/your_tenders_strings.dart';
class YourTendersFilterDialog extends StatelessWidget {
const YourTendersFilterDialog({super.key});
import '../../../core/constants/tender_approval_status.dart';
import '../../../core/constants/tender_feedback.dart';
Widget _dateRangePill(BuildContext context) {
final viewModel = context.watch<YourTendersViewModel>();
final start = viewModel.selectedDateRange?.start;
final end = viewModel.selectedDateRange?.end;
class YourTendersFilterDialog extends StatefulWidget {
final YourTendersViewModel viewModel;
final Function(DateTimeRange? dateRange, String? status) onFilterChanged;
const YourTendersFilterDialog({
required this.viewModel,
required this.onFilterChanged,
super.key,
});
Future<void> pick() async {
final picked = await showDateRangePicker(
context: context,
firstDate: DateTime(2020),
lastDate: DateTime(2035),
initialDateRange: viewModel.selectedDateRange,
);
if (picked != null) {
viewModel.setDateRange(picked);
}
}
@override
State<YourTendersFilterDialog> createState() =>
_YourTendersFilterDialogState();
}
return 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(
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,
),
),
],
),
],
),
),
),
class _YourTendersFilterDialogState extends State<YourTendersFilterDialog> {
DateTimeRange? dateRange;
String? status = YourTendersStrings.all;
final _fmt = DateFormat('yyyy/MM/dd');
String get startDateFormatted =>
dateRange?.start != null ? _fmt.format(dateRange!.start) : 'YYYY/MM/DD';
String get endDateFormatted =>
dateRange?.end != null ? _fmt.format(dateRange!.end) : 'YYYY/MM/DD';
String get startDateUnix =>
dateRange?.start != null
? (dateRange!.start.millisecondsSinceEpoch ~/ 1000).toString()
: '';
String get endDateUnix =>
dateRange?.end != null
? (dateRange!.end.millisecondsSinceEpoch ~/ 1000).toString()
: '';
@override
void initState() {
super.initState();
dateRange = widget.viewModel.selectedDateRange;
status = _mapApiValueToDisplayString(widget.viewModel.selectedStatus);
}
Future<void> pick() async {
final picked = await showDateRangePicker(
context: context,
firstDate: DateTime(2000),
lastDate: DateTime(2050),
initialDateRange: dateRange,
);
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
Widget build(BuildContext context) {
return Consumer<YourTendersViewModel>(
builder: (context, vm, child) {
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Text(
YourTendersStrings.filter,
style: TextStyle(
fontSize: 20.0.sp(),
fontWeight: FontWeight.w700,
color: AppColors.grey70,
),
return SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Text(
YourTendersStrings.filter,
style: TextStyle(
fontSize: 20.0.sp(),
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(
fontSize: 16.0.sp(),
fontWeight: FontWeight.w600,
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,
fontWeight: FontWeight.w400,
color: AppColors.grey70,
),
),
...[
YourTendersStrings.all,
YourTendersStrings.likeTenders,
YourTendersStrings.dislikeTenders,
YourTendersStrings.approvedTenders,
YourTendersStrings.tendersSubmitted,
].map((status) {
return RadioListTile(
value: status,
groupValue: vm.selectedStatus,
activeColor: AppColors.mainBlue,
onChanged: (value) => vm.setStatus(value!),
title: Text(
status,
style: TextStyle(
fontSize: 16.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey70,
),
),
);
}),
SizedBox(height: 16.0.h()),
SizedBox(
width: double.infinity,
child: BaseButton(
isEnabled: true,
onPressed: () {
context.pop();
widget.onFilterChanged(
dateRange,
_mapDisplayStringToApiValue(status!),
);
}),
SizedBox(height: 16.0.h()),
SizedBox(
width: double.infinity,
child: BaseButton(
isEnabled: true,
onPressed: () {
context.pop();
vm.callWithFilter();
},
text: YourTendersStrings.confirm,
textColor: AppColors.textBlue,
backgroundColor: AppColors.primary20,
),
),
],
},
text: YourTendersStrings.confirm,
textColor: AppColors.textBlue,
backgroundColor: AppColors.primary20,
),
),
),
);
},
],
),
),
);
}
}