Fixed pagination tenders for desktop and
added new config for notification
This commit is contained in:
@@ -7,6 +7,8 @@ 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/base_button.dart';
|
||||
import 'package:tm_app/views/shared/desktop_navigation_widget.dart';
|
||||
import 'package:tm_app/views/shared/page_selection_dialog.dart';
|
||||
import 'package:tm_app/views/tenders/strings/tenders_strings.dart';
|
||||
import 'package:tm_app/views/tenders/widgets/main_tenders_slider.dart';
|
||||
|
||||
import '../../../core/constants/common_strings.dart';
|
||||
@@ -21,6 +23,7 @@ class DesktopTendersPage extends StatefulWidget {
|
||||
|
||||
class _DesktopTendersPageState extends State<DesktopTendersPage> {
|
||||
late final TendersViewModel viewModel;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -49,121 +52,49 @@ class _DesktopTendersPageState extends State<DesktopTendersPage> {
|
||||
backgroundColor: AppColors.backgroundColor,
|
||||
body: Column(
|
||||
children: [
|
||||
const DesktopNavigationWidget(
|
||||
currentIndex: 1, // Tenders index
|
||||
),
|
||||
|
||||
const DesktopNavigationWidget(currentIndex: 1),
|
||||
SizedBox(height: 48.0.h()),
|
||||
//search box
|
||||
SizedBox(
|
||||
height: 48.0.h(),
|
||||
width: 680,
|
||||
child: TextField(
|
||||
style: TextStyle(fontSize: 16.0.sp(), color: AppColors.grey0),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Search',
|
||||
hintStyle: TextStyle(
|
||||
fontSize: 16.0.sp(),
|
||||
color: AppColors.grey60,
|
||||
),
|
||||
prefixIcon: Padding(
|
||||
padding: EdgeInsets.all(12.0.w()),
|
||||
child: SvgPicture.asset(
|
||||
AssetsManager.normalSearch,
|
||||
width: 24.0.w(),
|
||||
height: 24.0.h(),
|
||||
),
|
||||
),
|
||||
filled: true,
|
||||
fillColor: AppColors.grey0,
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
horizontal: 12.0.w(),
|
||||
vertical: 12.0.h(),
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12.0.w()),
|
||||
borderSide: const BorderSide(
|
||||
color: AppColors.mainBlue,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12.0.w()),
|
||||
borderSide: BorderSide(color: AppColors.grey40, width: 1),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12.0.w()),
|
||||
borderSide: const BorderSide(
|
||||
color: AppColors.grey,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: EdgeInsets.only(top: 25.0.h()),
|
||||
height: 40.0.h(),
|
||||
width: 680,
|
||||
child: Row(
|
||||
children: [
|
||||
BaseButton(
|
||||
width: 178.0.w(),
|
||||
isEnabled: true,
|
||||
onPressed: () {},
|
||||
text: 'Filter',
|
||||
icon: AssetsManager.filter,
|
||||
iconColor: AppColors.textBlue,
|
||||
backgroundColor: AppColors.primary2,
|
||||
textColor: AppColors.textBlue,
|
||||
),
|
||||
SizedBox(width: 10.0.w()),
|
||||
BaseButton(
|
||||
width: 178.0.w(),
|
||||
isEnabled: true,
|
||||
onPressed: () {},
|
||||
text: 'Sort',
|
||||
icon: AssetsManager.sort,
|
||||
iconColor: AppColors.textBlue,
|
||||
backgroundColor: AppColors.primary2,
|
||||
textColor: AppColors.textBlue,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const _SearchBox(),
|
||||
const _ActionButtons(),
|
||||
Expanded(
|
||||
child: Consumer<TendersViewModel>(
|
||||
builder: (context, viewModel, child) {
|
||||
if (viewModel.isLoading) {
|
||||
return const Expanded(
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: AppColors.jellyBean,
|
||||
),
|
||||
),
|
||||
builder: (context, vm, child) {
|
||||
if (vm.isLoading) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(color: AppColors.jellyBean),
|
||||
);
|
||||
}
|
||||
if (viewModel.errorMessage != null) {
|
||||
return Center(child: Text(viewModel.errorMessage!));
|
||||
if (vm.errorMessage != null) {
|
||||
return Center(child: Text(vm.errorMessage!));
|
||||
}
|
||||
if ((viewModel.tendersResponse?.data?.tenders == null ||
|
||||
viewModel.tendersResponse!.data!.tenders!.isEmpty) &&
|
||||
viewModel.isLoading == false) {
|
||||
final tenders = vm.tendersResponse?.data?.tenders ?? [];
|
||||
if (tenders.isEmpty) {
|
||||
return const Center(child: Text(CommonStrings.noData));
|
||||
}
|
||||
return SingleChildScrollView(
|
||||
child: SizedBox(
|
||||
width: 740,
|
||||
height: 800,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 4.0.h()),
|
||||
child: MainTendersSlider(
|
||||
tenders: viewModel.tendersResponse?.data?.tenders ?? [],
|
||||
isDesktop: true,
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: SizedBox(
|
||||
width: 740,
|
||||
height: 800,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 4.0.h()),
|
||||
child: MainTendersSlider(
|
||||
tenders: tenders,
|
||||
isDesktop: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
_DesktopPagination(
|
||||
totalPages: vm.totalPages,
|
||||
currentPage: vm.currentPage,
|
||||
onPageSelected: vm.jumpToPage,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -173,3 +104,174 @@ class _DesktopTendersPageState extends State<DesktopTendersPage> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SearchBox extends StatelessWidget {
|
||||
const _SearchBox();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 48.0.h(),
|
||||
width: 680,
|
||||
child: TextField(
|
||||
style: TextStyle(fontSize: 16.0.sp(), color: AppColors.grey0),
|
||||
decoration: InputDecoration(
|
||||
hintText: 'Search',
|
||||
hintStyle: TextStyle(fontSize: 16.0.sp(), color: AppColors.grey60),
|
||||
prefixIcon: Padding(
|
||||
padding: EdgeInsets.all(12.0.w()),
|
||||
child: SvgPicture.asset(
|
||||
AssetsManager.normalSearch,
|
||||
width: 24.0.w(),
|
||||
height: 24.0.h(),
|
||||
),
|
||||
),
|
||||
filled: true,
|
||||
fillColor: AppColors.grey0,
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
horizontal: 12.0.w(),
|
||||
vertical: 12.0.h(),
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12.0.w()),
|
||||
borderSide: const BorderSide(color: AppColors.mainBlue, width: 1),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12.0.w()),
|
||||
borderSide: BorderSide(color: AppColors.grey40, width: 1),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12.0.w()),
|
||||
borderSide: const BorderSide(color: AppColors.grey, width: 1.5),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ActionButtons extends StatelessWidget {
|
||||
const _ActionButtons();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: EdgeInsets.only(top: 25.0.h()),
|
||||
height: 40.0.h(),
|
||||
width: 680,
|
||||
child: Row(
|
||||
children: [
|
||||
BaseButton(
|
||||
width: 178.0.w(),
|
||||
isEnabled: true,
|
||||
onPressed: () {},
|
||||
text: 'Filter',
|
||||
icon: AssetsManager.filter,
|
||||
iconColor: AppColors.textBlue,
|
||||
backgroundColor: AppColors.primary2,
|
||||
textColor: AppColors.textBlue,
|
||||
),
|
||||
SizedBox(width: 10.0.w()),
|
||||
BaseButton(
|
||||
width: 178.0.w(),
|
||||
isEnabled: true,
|
||||
onPressed: () {},
|
||||
text: 'Sort',
|
||||
icon: AssetsManager.sort,
|
||||
iconColor: AppColors.textBlue,
|
||||
backgroundColor: AppColors.primary2,
|
||||
textColor: AppColors.textBlue,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DesktopPagination extends StatelessWidget {
|
||||
final int totalPages;
|
||||
final int currentPage;
|
||||
final ValueChanged<int> onPageSelected;
|
||||
|
||||
const _DesktopPagination({
|
||||
required this.totalPages,
|
||||
required this.currentPage,
|
||||
required this.onPageSelected,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: 680,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 12.0.h()),
|
||||
child: Row(
|
||||
children: [
|
||||
const Spacer(),
|
||||
Text(
|
||||
TendersStrings.page,
|
||||
style: TextStyle(
|
||||
fontSize: 13.0.sp(),
|
||||
fontWeight: FontWeight.w300,
|
||||
color: AppColors.grey60,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 10.0.w()),
|
||||
InkWell(
|
||||
onTap: () async {
|
||||
final selectedPage = await showDialog<int>(
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
PageSelectionDialog(totalPages: totalPages),
|
||||
);
|
||||
if (selectedPage != null) {
|
||||
onPageSelected(selectedPage);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: AppColors.grey30, width: 1),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(width: 5.0.w()),
|
||||
Text(
|
||||
'$currentPage',
|
||||
style: TextStyle(
|
||||
fontSize: 14.0.sp(),
|
||||
fontWeight: FontWeight.w500,
|
||||
color: AppColors.grey70,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 5.0.w()),
|
||||
SvgPicture.asset(AssetsManager.arrowDownSmall),
|
||||
SizedBox(width: 5.0.w()),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(width: 5.0.w()),
|
||||
Text(
|
||||
TendersStrings.of,
|
||||
style: TextStyle(
|
||||
fontSize: 14.0.sp(),
|
||||
fontWeight: FontWeight.w400,
|
||||
color: AppColors.grey60,
|
||||
),
|
||||
),
|
||||
SizedBox(width: 5.0.w()),
|
||||
Text(
|
||||
'$totalPages',
|
||||
style: TextStyle(
|
||||
fontSize: 13.0.sp(),
|
||||
fontWeight: FontWeight.w400,
|
||||
color: AppColors.grey60,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,4 +10,6 @@ class TendersStrings {
|
||||
static const String tenderDeadlineLabel = 'Deadline';
|
||||
static const String contracts = 'Contracts';
|
||||
static const String notifications = 'Notifications';
|
||||
static const String of= 'Of';
|
||||
static const String page = 'Page';
|
||||
}
|
||||
|
||||
@@ -27,6 +27,31 @@ class _MainTendersSliderState extends State<MainTendersSlider> {
|
||||
final PageController pageController = PageController(initialPage: 0);
|
||||
int currentPage = 1;
|
||||
|
||||
ScrollController? _scrollController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
if (widget.isDesktop) {
|
||||
_scrollController = ScrollController();
|
||||
_scrollController!.addListener(_onScroll);
|
||||
}
|
||||
}
|
||||
|
||||
void _onScroll() {
|
||||
if (!widget.isDesktop) {
|
||||
return;
|
||||
}
|
||||
final viewModel = context.read<TendersViewModel>();
|
||||
if (_scrollController!.position.pixels >=
|
||||
_scrollController!.position.maxScrollExtent - 200 &&
|
||||
viewModel.hasMoreData &&
|
||||
!viewModel.isLoadingMore) {
|
||||
viewModel.loadMoreTenders();
|
||||
}
|
||||
}
|
||||
|
||||
void _goToPreviousPage() {
|
||||
if (currentPage == 1) {
|
||||
return;
|
||||
@@ -88,6 +113,7 @@ class _MainTendersSliderState extends State<MainTendersSlider> {
|
||||
@override
|
||||
void dispose() {
|
||||
pageController.dispose();
|
||||
_scrollController?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -98,19 +124,6 @@ class _MainTendersSliderState extends State<MainTendersSlider> {
|
||||
return widget.isDesktop ? _tendersListView() : _tendersSlider(isLtr);
|
||||
}
|
||||
|
||||
Widget _tendersListView() {
|
||||
return ListView.separated(
|
||||
padding: EdgeInsets.all(16.0.h()),
|
||||
itemCount: widget.tenders.length,
|
||||
separatorBuilder:
|
||||
(_, __) => SizedBox(height: widget.isDesktop ? 0 : 16.0.h()),
|
||||
itemBuilder: (context, index) {
|
||||
final tender = widget.tenders[index];
|
||||
return TenderCard(tender: tender, isDesktop: widget.isDesktop);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _tendersSlider(bool isLtr) {
|
||||
return Stack(
|
||||
children: [_tendersPageView(), _nextPreviousArrows(isLtr: isLtr)],
|
||||
@@ -251,4 +264,38 @@ class _MainTendersSliderState extends State<MainTendersSlider> {
|
||||
)
|
||||
: SizedBox(width: 32.0.h(), height: 32.0.h());
|
||||
}
|
||||
|
||||
Widget _tendersListView() {
|
||||
final viewModel = context.watch<TendersViewModel>();
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView.separated(
|
||||
padding: EdgeInsets.all(16.0.h()),
|
||||
itemCount:
|
||||
widget.tenders.length +
|
||||
(widget.isDesktop && viewModel.isLoadingMore ? 1 : 0),
|
||||
separatorBuilder:
|
||||
(_, __) => SizedBox(height: widget.isDesktop ? 0 : 16.0.h()),
|
||||
itemBuilder: (context, index) {
|
||||
if (index < widget.tenders.length) {
|
||||
final tender = widget.tenders[index];
|
||||
return TenderCard(tender: tender, isDesktop: widget.isDesktop);
|
||||
} else {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2.0,
|
||||
color: AppColors.jellyBean,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user