Refactor tender dialogs to utilize ViewModel and improve state management
- Updated `TendersFilterDialog` and `TendersSortDialog` to use a stateful approach, allowing for better management of selected filters and sorting options. - Integrated `TendersViewModel` into both dialogs, enabling direct interaction with the view model for setting date ranges and statuses. - Enhanced the UI to reflect changes in state, ensuring a more responsive user experience when applying filters and sorting. - Updated the `d_tenders_page.dart` to pass the view model and handle filter and sort actions appropriately.
This commit is contained in:
@@ -185,6 +185,7 @@ class _ActionButtons extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final viewModel = context.read<TendersViewModel>();
|
||||||
return Container(
|
return Container(
|
||||||
margin: EdgeInsets.only(top: 25.0.h()),
|
margin: EdgeInsets.only(top: 25.0.h()),
|
||||||
height: 40.0.h(),
|
height: 40.0.h(),
|
||||||
@@ -203,9 +204,16 @@ class _ActionButtons extends StatelessWidget {
|
|||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
child: const SizedBox(
|
child: SizedBox(
|
||||||
width: 560,
|
width: 560,
|
||||||
child: TendersFilterDialog(),
|
child: TendersFilterDialog(
|
||||||
|
viewModel: viewModel,
|
||||||
|
onFilterChanged: (dateRange, status) {
|
||||||
|
viewModel.setDateRange(dateRange);
|
||||||
|
viewModel.setStatus(status!);
|
||||||
|
viewModel.callWithSortAndStatus();
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -229,9 +237,15 @@ class _ActionButtons extends StatelessWidget {
|
|||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
child: const SizedBox(
|
child: SizedBox(
|
||||||
width: 560,
|
width: 560,
|
||||||
child: TendersSortDialog(),
|
child: TendersSortDialog(
|
||||||
|
viewModel: viewModel,
|
||||||
|
onConfirm: (sort) {
|
||||||
|
viewModel.setSort(sort);
|
||||||
|
viewModel.callWithSortAndStatus();
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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';
|
||||||
@@ -10,96 +10,72 @@ import 'package:tm_app/views/tenders/strings/tenders_strings.dart';
|
|||||||
|
|
||||||
import '../../../view_models/tenders_view_model.dart';
|
import '../../../view_models/tenders_view_model.dart';
|
||||||
|
|
||||||
class TendersFilterDialog extends StatelessWidget {
|
class TendersFilterDialog extends StatefulWidget {
|
||||||
const TendersFilterDialog({super.key});
|
final TendersViewModel viewModel;
|
||||||
|
final Function(DateTimeRange? dateRange, String? status) onFilterChanged;
|
||||||
|
const TendersFilterDialog({
|
||||||
|
required this.viewModel,
|
||||||
|
required this.onFilterChanged,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
Widget _dateRangePill(BuildContext context) {
|
@override
|
||||||
final viewModel = context.watch<TendersViewModel>();
|
State<TendersFilterDialog> createState() => _TendersFilterDialogState();
|
||||||
final start = viewModel.selectedDateRange?.start;
|
}
|
||||||
final end = viewModel.selectedDateRange?.end;
|
|
||||||
|
class _TendersFilterDialogState extends State<TendersFilterDialog> {
|
||||||
|
DateTimeRange? dateRange;
|
||||||
|
String? status = TendersStrings.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _mapApiValueToDisplayString(String apiValue) {
|
||||||
|
final mapping = {
|
||||||
|
'All': TendersStrings.all,
|
||||||
|
'Read': TendersStrings.read,
|
||||||
|
'Unread': TendersStrings.unread,
|
||||||
|
};
|
||||||
|
return mapping[apiValue] ?? TendersStrings.all;
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> pick() async {
|
Future<void> pick() async {
|
||||||
final picked = await showDateRangePicker(
|
final picked = await showDateRangePicker(
|
||||||
context: context,
|
context: context,
|
||||||
firstDate: DateTime(2020),
|
firstDate: DateTime(2000),
|
||||||
lastDate: DateTime(2035),
|
lastDate: DateTime(2050),
|
||||||
initialDateRange: viewModel.selectedDateRange,
|
initialDateRange: dateRange,
|
||||||
);
|
);
|
||||||
if (picked != null) {
|
if (picked != null) {
|
||||||
viewModel.setDateRange(picked);
|
dateRange = picked;
|
||||||
|
setState(() {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return SizedBox(
|
|
||||||
width: 560,
|
|
||||||
child: 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
|
|
||||||
? TendersStrings.startDate
|
|
||||||
: viewModel.startDateFormatted,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16.0.sp(),
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
color: AppColors.grey70,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
end == null
|
|
||||||
? TendersStrings.endDate
|
|
||||||
: viewModel.endDateFormatted,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16.0.sp(),
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
color: AppColors.grey70,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Consumer<TendersViewModel>(
|
|
||||||
builder: (context, vm, child) {
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
@@ -144,7 +120,70 @@ class TendersFilterDialog extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 10.0.h()),
|
SizedBox(height: 10.0.h()),
|
||||||
_dateRangePill(context),
|
SizedBox(
|
||||||
|
width: 560,
|
||||||
|
child: 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
|
||||||
|
? TendersStrings.startDate
|
||||||
|
: startDateFormatted,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16.0.sp(),
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: AppColors.grey70,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
dateRange?.end == null
|
||||||
|
? TendersStrings.endDate
|
||||||
|
: endDateFormatted,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16.0.sp(),
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
color: AppColors.grey70,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
SizedBox(height: 16.0.h()),
|
SizedBox(height: 16.0.h()),
|
||||||
Text(
|
Text(
|
||||||
TendersStrings.status,
|
TendersStrings.status,
|
||||||
@@ -161,9 +200,12 @@ class TendersFilterDialog extends StatelessWidget {
|
|||||||
].map((status) {
|
].map((status) {
|
||||||
return RadioListTile(
|
return RadioListTile(
|
||||||
value: status,
|
value: status,
|
||||||
groupValue: vm.selectedStatus,
|
groupValue: this.status,
|
||||||
activeColor: AppColors.mainBlue,
|
activeColor: AppColors.mainBlue,
|
||||||
onChanged: (value) => vm.setStatus(value!),
|
onChanged:
|
||||||
|
(value) => setState(() {
|
||||||
|
this.status = value!;
|
||||||
|
}),
|
||||||
title: Text(
|
title: Text(
|
||||||
status,
|
status,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -184,7 +226,7 @@ class TendersFilterDialog extends StatelessWidget {
|
|||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.pop();
|
context.pop();
|
||||||
vm.callWithSortAndStatus();
|
widget.onFilterChanged(dateRange, status);
|
||||||
},
|
},
|
||||||
text: TendersStrings.confirm,
|
text: TendersStrings.confirm,
|
||||||
textColor: AppColors.textBlue,
|
textColor: AppColors.textBlue,
|
||||||
@@ -197,7 +239,5 @@ class TendersFilterDialog extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
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: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';
|
||||||
@@ -10,13 +9,30 @@ import 'package:tm_app/views/tenders/strings/tenders_strings.dart';
|
|||||||
|
|
||||||
import '../../../view_models/tenders_view_model.dart';
|
import '../../../view_models/tenders_view_model.dart';
|
||||||
|
|
||||||
class TendersSortDialog extends StatelessWidget {
|
class TendersSortDialog extends StatefulWidget {
|
||||||
const TendersSortDialog({super.key});
|
const TendersSortDialog({
|
||||||
|
required this.viewModel,
|
||||||
|
required this.onConfirm,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
final TendersViewModel viewModel;
|
||||||
|
final ValueChanged<String> onConfirm;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<TendersSortDialog> createState() => _TendersSortDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TendersSortDialogState extends State<TendersSortDialog> {
|
||||||
|
String sort = '';
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
sort = widget.viewModel.selectedSort;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Consumer<TendersViewModel>(
|
|
||||||
builder: (context, vm, child) {
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
@@ -52,15 +68,17 @@ class TendersSortDialog extends StatelessWidget {
|
|||||||
SizedBox(height: 16.0.h()),
|
SizedBox(height: 16.0.h()),
|
||||||
Divider(color: AppColors.grey20),
|
Divider(color: AppColors.grey20),
|
||||||
SizedBox(height: 20.0.h()),
|
SizedBox(height: 20.0.h()),
|
||||||
...[
|
...[TendersStrings.minimumTime, TendersStrings.mostOfTheTime].map((
|
||||||
TendersStrings.minimumTime,
|
status,
|
||||||
TendersStrings.mostOfTheTime,
|
) {
|
||||||
].map((status) {
|
|
||||||
return RadioListTile(
|
return RadioListTile(
|
||||||
value: status,
|
value: status,
|
||||||
groupValue: vm.selectedSort,
|
groupValue: sort,
|
||||||
activeColor: AppColors.mainBlue,
|
activeColor: AppColors.mainBlue,
|
||||||
onChanged: (value) => vm.setSort(value!),
|
onChanged:
|
||||||
|
(value) => setState(() {
|
||||||
|
sort = value!;
|
||||||
|
}),
|
||||||
title: Text(
|
title: Text(
|
||||||
status,
|
status,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
@@ -81,7 +99,7 @@ class TendersSortDialog extends StatelessWidget {
|
|||||||
isEnabled: true,
|
isEnabled: true,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.pop();
|
context.pop();
|
||||||
vm.callWithSortAndStatus();
|
widget.onConfirm(sort);
|
||||||
},
|
},
|
||||||
text: TendersStrings.confirm,
|
text: TendersStrings.confirm,
|
||||||
textColor: AppColors.textBlue,
|
textColor: AppColors.textBlue,
|
||||||
@@ -94,7 +112,5 @@ class TendersSortDialog extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user