diff --git a/assets/icons/arrow_down_small.svg b/assets/icons/arrow_down_small.svg
new file mode 100644
index 0000000..c4af0cf
--- /dev/null
+++ b/assets/icons/arrow_down_small.svg
@@ -0,0 +1,3 @@
+
diff --git a/lib/core/constants/assets.dart b/lib/core/constants/assets.dart
index 7dbc80d..980c0f0 100644
--- a/lib/core/constants/assets.dart
+++ b/lib/core/constants/assets.dart
@@ -62,5 +62,6 @@ class AssetsManager {
//Liked tenders
static const trash = 'assets/icons/trash.svg';
+ static const arrowdownSmall = 'assets/icons/arrow_down_small.svg';
}
diff --git a/lib/view_models/liked_tenders_view_model.dart b/lib/view_models/liked_tenders_view_model.dart
index 931a1c0..28dcf9a 100644
--- a/lib/view_models/liked_tenders_view_model.dart
+++ b/lib/view_models/liked_tenders_view_model.dart
@@ -17,67 +17,75 @@ class LikedTendersViewModel with ChangeNotifier {
LikedTendersResponse? data;
int _offset = 0;
- final int _limit = 20;
+ final int _limit = 7;
- Future getLikedTenders({bool reset = false}) async {
- if (!reset && (isLoadingMore || !hasMore)) {
- return;
- }
+ int currentPage = 1;
- if (reset) {
- _offset = 0;
- hasMore = true;
- }
+Future getLikedTenders({bool reset = false}) async {
+ if (!reset && (isLoadingMore || !hasMore)) {
+ return;
+ }
+ if (reset) {
+ _offset = (currentPage - 1) * _limit;
+ hasMore = true;
+
+ data = null;
+ isLoading = true;
+ notifyListeners();
+ } else {
if (_offset == 0) {
isLoading = true;
} else {
isLoadingMore = true;
}
- errorMessage = null;
- notifyListeners();
-
- final result = await likedTendersRepository.getLikedTenders(
- offset: _offset,
- limit: _limit,
- );
-
- switch (result) {
- case Ok():
- final incoming = result.value;
- final incomingList = incoming.data?.feedback ?? [];
-
- if (reset || data == null) {
- data = incoming;
- } else {
- final existingList = data!.data!.feedback ?? [];
- final combined = List.of(existingList)
- ..addAll(incomingList);
-
- final newMeta = incoming.data?.meta ?? data!.data!.meta;
-
- data = data!.copyWith(
- data: data!.data!.copyWith(feedback: combined, meta: newMeta),
- );
- }
-
- // چون offset بر اساس limit حرکت میکنه
- hasMore = incomingList.length == _limit;
- _offset += _limit;
-
- errorMessage = null;
- break;
-
- case Error():
- errorMessage = result.error.toString();
- break;
- }
-
- isLoading = false;
- isLoadingMore = false;
notifyListeners();
}
+ errorMessage = null;
+
+ final result = await likedTendersRepository.getLikedTenders(
+ offset: _offset,
+ limit: _limit,
+ );
+
+ switch (result) {
+ case Ok():
+ final incoming = result.value;
+ final incomingList = incoming.data?.feedback ?? [];
+
+ if (reset) {
+ data = incoming;
+ } else {
+ final existingList = data?.data?.feedback ?? [];
+ final combined = List.of(existingList)
+ ..addAll(incomingList);
+
+ data = data?.copyWith(
+ data: data?.data?.copyWith(
+ feedback: combined,
+ meta: incoming.data?.meta ?? data!.data!.meta,
+ ),
+ );
+ }
+
+ hasMore = incomingList.length == _limit;
+ _offset += _limit;
+
+ errorMessage = null;
+ break;
+
+ case Error():
+ errorMessage = result.error.toString();
+ break;
+ }
+
+ isLoading = false;
+ isLoadingMore = false;
+ notifyListeners();
+}
+
+
Future loadMoreIfNeeded() async {
if (isLoadingMore || !hasMore) {
return;
@@ -85,6 +93,13 @@ class LikedTendersViewModel with ChangeNotifier {
await getLikedTenders(reset: false);
}
+ Future jumpToPage(int page) async {
+ currentPage = page;
+ _offset = (page - 1) * _limit;
+ hasMore = true;
+ await getLikedTenders(reset: true);
+ }
+
void removeTenderById(String id) {
final current = data;
final list = current?.data?.feedback;
@@ -92,14 +107,16 @@ class LikedTendersViewModel with ChangeNotifier {
return;
}
- final List updatedList = List.of(
- list,
- )..removeWhere((e) => e.id == id);
+ final updatedList = List.of(list)
+ ..removeWhere((e) => e.id == id);
final updatedMeta = current.data?.meta?.copyWith(total: updatedList.length);
data = current.copyWith(
- data: current.data?.copyWith(feedback: updatedList, meta: updatedMeta),
+ data: current.data?.copyWith(
+ feedback: updatedList,
+ meta: updatedMeta,
+ ),
);
notifyListeners();
diff --git a/lib/views/liked_tenders/pages/liked_tenders_desktop_page.dart b/lib/views/liked_tenders/pages/liked_tenders_desktop_page.dart
index 7fe511c..42a1584 100644
--- a/lib/views/liked_tenders/pages/liked_tenders_desktop_page.dart
+++ b/lib/views/liked_tenders/pages/liked_tenders_desktop_page.dart
@@ -19,39 +19,10 @@ class LikedTendersDesktopPage extends StatefulWidget {
}
class _LikedTendersDesktopPageState extends State {
- late ScrollController _scrollController;
-
- @override
- void initState() {
- super.initState();
- _scrollController = ScrollController()..addListener(_onScroll);
- }
-
- void _onScroll() {
- if (!_scrollController.hasClients) {
- return;
- }
- final max = _scrollController.position.maxScrollExtent;
- final pos = _scrollController.position.pixels;
- const threshold = 200.0;
- if (max - pos <= threshold) {
- final vm = Provider.of(context, listen: false);
- vm.loadMoreIfNeeded();
- }
- }
-
- @override
- void dispose() {
- _scrollController.removeListener(_onScroll);
- _scrollController.dispose();
- super.dispose();
- }
-
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.backgroundColor,
-
body: Column(
children: [
DesktopNavigationWidget(
@@ -72,93 +43,171 @@ class _LikedTendersDesktopPageState extends State {
Expanded(
child: SizedBox(
width: 740,
- child: Column(
- children: [
- Expanded(
- child: Consumer(
- builder: (context, viewModel, child) {
- if (viewModel.isLoading &&
- (viewModel.data?.data?.feedback ?? []).isEmpty) {
- return const Center(
- child: CircularProgressIndicator(
- color: AppColors.secondary50,
- ),
- );
- }
+ child: Consumer(
+ builder: (context, viewModel, child) {
+ if (viewModel.isLoading &&
+ (viewModel.data?.data?.feedback ?? []).isEmpty) {
+ return const Center(
+ child: CircularProgressIndicator(
+ color: AppColors.secondary50,
+ ),
+ );
+ }
- if (viewModel.errorMessage != null) {
- return Center(child: Text(viewModel.errorMessage!));
- }
+ if (viewModel.errorMessage != null) {
+ return Center(child: Text(viewModel.errorMessage!));
+ }
- final feedback = viewModel.data?.data?.feedback ?? [];
+ final feedback = viewModel.data?.data?.feedback ?? [];
- if (feedback.isEmpty) {
- return const Center(
- child: Text('چیزی برای نمایش نیست'),
- );
- }
+ if (feedback.isEmpty) {
+ return const Center(child: Text('Nothing to show'));
+ }
- final itemCount =
- feedback.length + (viewModel.isLoadingMore ? 1 : 0);
+ final currentPage = viewModel.currentPage;
+ final totalPages = viewModel.data?.data?.meta?.pages ?? 1;
- return RefreshIndicator(
+ return Column(
+ children: [
+ Expanded(
+ child: RefreshIndicator(
onRefresh: () async {
await viewModel.getLikedTenders(reset: true);
},
child: ListView.builder(
- controller: _scrollController,
padding: EdgeInsets.symmetric(
horizontal: 24.0.w(),
vertical: 15.0.h(),
),
- itemCount: itemCount,
+ itemCount: feedback.length,
itemBuilder: (context, index) {
- if (index < feedback.length) {
- final item = feedback[index];
- return Padding(
- padding: EdgeInsets.only(bottom: 20.0.h()),
- child: Dismissible(
- key: ValueKey(item.id ?? index),
- direction: DismissDirection.endToStart,
- background: Container(
- alignment: Alignment.centerRight,
- padding: EdgeInsets.symmetric(
- horizontal: 20.0.w(),
- ),
- child: SvgPicture.asset(
- AssetsManager.trash,
- ),
+ final item = feedback[index];
+ return Padding(
+ padding: EdgeInsets.only(bottom: 20.0.h()),
+ child: Dismissible(
+ key: ValueKey(item.id ?? index),
+ direction: DismissDirection.endToStart,
+ background: Container(
+ alignment: Alignment.centerRight,
+ padding: EdgeInsets.symmetric(
+ horizontal: 20.0.w(),
),
- onDismissed: (_) {
- if (item.id != null) {
- viewModel.removeTenderById(item.id!);
- }
- },
- child: LikedListItem(tender: item.tender!),
- ),
- );
- } else {
- return const Padding(
- padding: EdgeInsets.symmetric(vertical: 20),
- child: Center(
- child: SizedBox(
- width: 24,
- height: 24,
- child: CircularProgressIndicator(
- color: AppColors.secondary50,
- strokeWidth: 2,
- ),
+ child: SvgPicture.asset(
+ AssetsManager.trash,
),
),
- );
- }
+ onDismissed: (_) {
+ if (item.id != null) {
+ viewModel.removeTenderById(item.id!);
+ }
+ },
+ child: LikedListItem(tender: item.tender!),
+ ),
+ );
},
),
- );
- },
- ),
- ),
- ],
+ ),
+ ),
+
+ SizedBox(height: 10.0.h()),
+
+ Row(
+ children: [
+ const Spacer(),
+ Text(
+ 'Page',
+ style: TextStyle(
+ fontSize: 13.0.sp(),
+ fontWeight: FontWeight.w300,
+ color: AppColors.grey60,
+ ),
+ ),
+ SizedBox(width: 10.0.w()),
+
+ GestureDetector(
+ onTap: () async {
+ final selectedPage = await showDialog(
+ context: context,
+ builder: (context) {
+ return AlertDialog(
+ title: const Text('Select Page'),
+ content: SizedBox(
+ width: 200,
+ height: 300,
+ child: ListView.builder(
+ itemCount: totalPages,
+ itemBuilder: (context, index) {
+ final pageNumber = index + 1;
+ return ListTile(
+ title: Text('Page $pageNumber'),
+ onTap: () =>
+ Navigator.pop(context, pageNumber),
+ );
+ },
+ ),
+ ),
+ );
+ },
+ );
+
+ if (selectedPage != null) {
+ await viewModel.jumpToPage(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(
+ '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,
+ ),
+ ),
+ ],
+ ),
+
+ SizedBox(height: 30.0.h()),
+ ],
+ );
+ },
),
),
),
diff --git a/lib/views/liked_tenders/pages/liked_tenders_mobile_page.dart b/lib/views/liked_tenders/pages/liked_tenders_mobile_page.dart
index f072287..0d1b8e1 100644
--- a/lib/views/liked_tenders/pages/liked_tenders_mobile_page.dart
+++ b/lib/views/liked_tenders/pages/liked_tenders_mobile_page.dart
@@ -71,7 +71,7 @@ class _LikedTendersMobilePageState extends State {
final feedback = viewModel.data?.data?.feedback ?? [];
if (feedback.isEmpty) {
- return const Center(child: Text('چیزی برای نمایش نیست'));
+ return const Center(child: Text('Nothing to show'));
}
final itemCount = feedback.length + (viewModel.isLoadingMore ? 1 : 0);
@@ -109,12 +109,12 @@ class _LikedTendersMobilePageState extends State {
),
);
} else {
- return const Padding(
- padding: EdgeInsets.symmetric(vertical: 20),
+ return Padding(
+ padding: EdgeInsets.symmetric(vertical: 20.0.h()),
child: Center(
child: SizedBox(
- width: 24,
- height: 24,
+ width: 24.0.w(),
+ height: 24.0.h(),
child: CircularProgressIndicator(
color: AppColors.secondary50,
strokeWidth: 2,
diff --git a/lib/views/liked_tenders/pages/liked_tenders_tablet_page.dart b/lib/views/liked_tenders/pages/liked_tenders_tablet_page.dart
index 37cf573..8e59ffe 100644
--- a/lib/views/liked_tenders/pages/liked_tenders_tablet_page.dart
+++ b/lib/views/liked_tenders/pages/liked_tenders_tablet_page.dart
@@ -21,34 +21,6 @@ class LikedTendersTabletPage extends StatefulWidget {
}
class _LikedTendersTabletPageState extends State {
- late final ScrollController _scrollController;
-
- @override
- void initState() {
- super.initState();
- _scrollController = ScrollController()..addListener(_onScroll);
- }
-
- void _onScroll() {
- if (!_scrollController.hasClients) {
- return;
- }
- final max = _scrollController.position.maxScrollExtent;
- final pos = _scrollController.position.pixels;
- const threshold = 200.0;
- if (max - pos <= threshold) {
- final vm = Provider.of(context, listen: false);
- vm.loadMoreIfNeeded();
- }
- }
-
- @override
- void dispose() {
- _scrollController.removeListener(_onScroll);
- _scrollController.dispose();
- super.dispose();
- }
-
@override
Widget build(BuildContext context) {
final GlobalKey key = GlobalKey();
@@ -96,24 +68,26 @@ class _LikedTendersTabletPageState extends State {
final feedback = viewModel.data?.data?.feedback ?? [];
if (feedback.isEmpty) {
- return const Center(child: Text('چیزی برای نمایش نیست'));
+ return const Center(child: Text('Nothing to show'));
}
- final itemCount =
- feedback.length + (viewModel.isLoadingMore ? 1 : 0);
- return RefreshIndicator(
- onRefresh: () async {
- await viewModel.getLikedTenders(reset: true);
- },
- child: ListView.builder(
- controller: _scrollController,
- padding: EdgeInsets.symmetric(
- horizontal: 24.0.w(),
- vertical: 15.0.h(),
- ),
- itemCount: itemCount,
- itemBuilder: (context, index) {
- if (index < feedback.length) {
+ final currentPage = viewModel.currentPage;
+ final totalPages = viewModel.data?.data?.meta?.pages ?? 1;
+
+ return Column(
+ children: [
+ Expanded(
+ child: RefreshIndicator(
+ onRefresh: () async {
+ await viewModel.getLikedTenders(reset: true);
+ },
+ child: ListView.builder(
+ padding: EdgeInsets.symmetric(
+ horizontal: 24.0.w(),
+ vertical: 15.0.h(),
+ ),
+ itemCount: feedback.length,
+ itemBuilder: (context, index) {
final item = feedback[index];
return Padding(
padding: EdgeInsets.only(bottom: 20.0.h()),
@@ -137,24 +111,114 @@ class _LikedTendersTabletPageState extends State {
child: LikedListItem(tender: item.tender!),
),
);
- } else {
- return const Padding(
- padding: EdgeInsets.symmetric(vertical: 20),
- child: Center(
- child: SizedBox(
- width: 24,
- height: 24,
- child: CircularProgressIndicator(
- color: AppColors.secondary50,
- strokeWidth: 2,
+ },
+ ),
+ ),
+ ),
+
+ SizedBox(height: 10.0.h()),
+
+ Row(
+ children: [
+ const Spacer(),
+ Text(
+ 'Page',
+ style: TextStyle(
+ fontSize: 13.0.sp(),
+ fontWeight: FontWeight.w300,
+ color: AppColors.grey60,
+ ),
+ ),
+ SizedBox(width: 10.0.w()),
+
+ GestureDetector(
+ onTap: () async {
+ final selectedPage = await showDialog(
+ context: context,
+ builder: (context) {
+ return AlertDialog(
+ title: const Text('Select Page'),
+ content: SizedBox(
+ width: 200,
+ height: 300,
+ child: ListView.builder(
+ itemCount: totalPages,
+ itemBuilder: (context, index) {
+ final pageNumber = index + 1;
+ return ListTile(
+ title: Text('Page $pageNumber'),
+ onTap:
+ () => Navigator.pop(
+ context,
+ pageNumber,
+ ),
+ );
+ },
+ ),
+ ),
+ );
+ },
+ );
+
+ if (selectedPage != null) {
+ await viewModel.jumpToPage(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(
+ '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,
+ ),
+ ),
+ SizedBox(width: 30.0.w()),
+ ],
+ ),
+
+ SizedBox(height: 30.0.h()),
+ ],
+ );
+ },
),
),
],
diff --git a/lib/views/login/pages/login_desktop_page.dart b/lib/views/login/pages/login_desktop_page.dart
index 028c510..468407f 100644
--- a/lib/views/login/pages/login_desktop_page.dart
+++ b/lib/views/login/pages/login_desktop_page.dart
@@ -131,7 +131,7 @@ class _LoginDesktopPageState extends State {
? Padding(
padding: EdgeInsets.symmetric(vertical: 20.0.h()),
child: ClipRRect(
- borderRadius: BorderRadiusGeometry.circular(32),
+ borderRadius: BorderRadius.circular(32),
child: Image.asset(
AssetsManager.webLoginImage,
width: 796.0,