From 8a289f2d0578d98b36f4427f3375abe8adee26fd Mon Sep 17 00:00:00 2001 From: llsajjad Date: Sat, 20 Sep 2025 23:09:00 +0330 Subject: [PATCH] added new design for tenders desktop --- assets/icons/search_normal.svg | 4 + assets/icons/sort.svg | 5 ++ lib/core/constants/assets.dart | 2 + lib/main.dart | 2 +- lib/views/shared/base_button.dart | 45 ++++++++-- lib/views/tenders/pages/d_tenders_page.dart | 85 ++++++++++++++++++- .../tenders/widgets/main_tenders_slider.dart | 60 ++++++++----- .../widgets/tender_action_buttons_row.dart | 10 +-- lib/views/tenders/widgets/tender_card.dart | 36 ++++---- 9 files changed, 199 insertions(+), 50 deletions(-) create mode 100644 assets/icons/search_normal.svg create mode 100644 assets/icons/sort.svg diff --git a/assets/icons/search_normal.svg b/assets/icons/search_normal.svg new file mode 100644 index 0000000..ff9b6e2 --- /dev/null +++ b/assets/icons/search_normal.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/sort.svg b/assets/icons/sort.svg new file mode 100644 index 0000000..bf462ba --- /dev/null +++ b/assets/icons/sort.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lib/core/constants/assets.dart b/lib/core/constants/assets.dart index 33252ae..8c821a0 100644 --- a/lib/core/constants/assets.dart +++ b/lib/core/constants/assets.dart @@ -40,6 +40,8 @@ class AssetsManager { static const arrowLeftSmall = 'assets/icons/arrow-left-small.svg'; static const arrowRightSmall = 'assets/icons/arrow-right-small.svg'; static const tick = 'assets/icons/tick.svg'; + static const normalSearch = 'assets/icons/search_normal.svg'; + static const sort = 'assets/icons/sort.svg'; //home page static const arrows = 'assets/icons/arrows.svg'; diff --git a/lib/main.dart b/lib/main.dart index 52e7bb3..bdc3c9e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -37,7 +37,7 @@ void main() async { final prefs = await SharedPreferences.getInstance(); List providers = [ - Provider(create: (context) => prefs), + Provider(create: (context) => prefs), ...providersRemote, ]; // Configure web URL strategy if running on web diff --git a/lib/views/shared/base_button.dart b/lib/views/shared/base_button.dart index 6eee16d..09c471d 100644 --- a/lib/views/shared/base_button.dart +++ b/lib/views/shared/base_button.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import '../../core/theme/colors.dart'; import '../../core/utils/size_config.dart'; @@ -16,6 +17,8 @@ class BaseButton extends StatelessWidget { final double? elevation; final double? width; final bool isLoading; + final String? icon; + final Color? iconColor; const BaseButton({ required this.isEnabled, @@ -29,6 +32,8 @@ class BaseButton extends StatelessWidget { this.elevation, this.width, this.isLoading = false, + this.icon, + this.iconColor, super.key, }); @@ -72,14 +77,38 @@ class BaseButton extends StatelessWidget { ), ), ), - child: Text( - text ?? LoginStrings.loginButton, - style: TextStyle( - fontSize: 16.0.sp(), - fontWeight: FontWeight.bold, - color: textColor ?? AppColors.white, - ), - ), + child: + icon != null + ? Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset( + icon!, + colorFilter: ColorFilter.mode( + iconColor ?? AppColors.white, + BlendMode.srcIn, + ), + ), + SizedBox(width: 7.0.w()), + Text( + text ?? LoginStrings.loginButton, + style: TextStyle( + fontSize: 16.0.sp(), + fontWeight: FontWeight.bold, + color: textColor ?? AppColors.white, + ), + ), + ], + ) + : Text( + text ?? LoginStrings.loginButton, + style: TextStyle( + fontSize: 16.0.sp(), + fontWeight: FontWeight.bold, + color: textColor ?? AppColors.white, + ), + ), ), ); } diff --git a/lib/views/tenders/pages/d_tenders_page.dart b/lib/views/tenders/pages/d_tenders_page.dart index 5ec9d4e..6ce2c02 100644 --- a/lib/views/tenders/pages/d_tenders_page.dart +++ b/lib/views/tenders/pages/d_tenders_page.dart @@ -1,8 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:provider/provider.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'; 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/tenders/widgets/main_tenders_slider.dart'; @@ -49,6 +52,86 @@ class _DesktopTendersPageState extends State { const DesktopNavigationWidget( currentIndex: 1, // Tenders index ), + + 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, + ), + ], + ), + ), + Expanded( child: Consumer( builder: (context, viewModel, child) { @@ -74,7 +157,7 @@ class _DesktopTendersPageState extends State { width: 740, height: 800, child: Padding( - padding: EdgeInsets.symmetric(vertical: 24.0.h()), + padding: EdgeInsets.symmetric(vertical: 4.0.h()), child: MainTendersSlider( tenders: viewModel.tendersResponse?.data?.tenders ?? [], isDesktop: true, diff --git a/lib/views/tenders/widgets/main_tenders_slider.dart b/lib/views/tenders/widgets/main_tenders_slider.dart index 1ff7aae..8170668 100644 --- a/lib/views/tenders/widgets/main_tenders_slider.dart +++ b/lib/views/tenders/widgets/main_tenders_slider.dart @@ -15,6 +15,7 @@ class MainTendersSlider extends StatefulWidget { super.key, this.isDesktop = false, }); + final bool isDesktop; final List tenders; @@ -24,7 +25,6 @@ class MainTendersSlider extends StatefulWidget { class _MainTendersSliderState extends State { final PageController pageController = PageController(initialPage: 0); - int currentPage = 1; void _goToPreviousPage() { @@ -32,17 +32,22 @@ class _MainTendersSliderState extends State { return; } + final targetIndex = currentPage - 2; + if (targetIndex < 0) { + return; + } + pageController.previousPage( duration: const Duration(milliseconds: 300), curve: Curves.easeInOutCubic, ); + setState(() { currentPage = currentPage - 1; }); - // Fetch feedback and approvals for the previous tender if not already loaded final viewModel = context.read(); - final previousTenderId = widget.tenders[currentPage - 2].id!; + final previousTenderId = widget.tenders[targetIndex].id!; if (!viewModel.hasFeedbackForTender(previousTenderId)) { viewModel.getTenderFeedback(previousTenderId); } @@ -56,17 +61,22 @@ class _MainTendersSliderState extends State { return; } + final targetIndex = currentPage; + if (targetIndex >= widget.tenders.length) { + return; + } + pageController.nextPage( duration: const Duration(milliseconds: 300), curve: Curves.easeInOutCubic, ); + setState(() { currentPage = currentPage + 1; }); - // Fetch feedback and approvals for the next tender if not already loaded final viewModel = context.read(); - final nextTenderId = widget.tenders[currentPage].id!; + final nextTenderId = widget.tenders[targetIndex].id!; if (!viewModel.hasFeedbackForTender(nextTenderId)) { viewModel.getTenderFeedback(nextTenderId); } @@ -83,11 +93,27 @@ class _MainTendersSliderState extends State { @override Widget build(BuildContext context) { - bool isLtr = Directionality.of(context) == TextDirection.ltr; - return SingleChildScrollView( - child: Stack( - children: [_tendersPageView(), _nextPreviousArrows(isLtr: isLtr)], - ), + final bool isLtr = Directionality.of(context) == TextDirection.ltr; + + 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)], ); } @@ -97,25 +123,21 @@ class _MainTendersSliderState extends State { height: 722.0.h(), child: PageView.builder( controller: pageController, - scrollDirection: Axis.horizontal, itemCount: widget.tenders.length, onPageChanged: (index) { setState(() { currentPage = index + 1; }); - // Fetch feedback and approvals for the new tender if not already loaded final viewModel = context.read(); - if (!viewModel.hasFeedbackForTender(widget.tenders[index].id!)) { - viewModel.getTenderFeedback(widget.tenders[index].id!); + final id = widget.tenders[index].id!; + if (!viewModel.hasFeedbackForTender(id)) { + viewModel.getTenderFeedback(id); } - if (!viewModel.hasTenderApprovalForTender( - widget.tenders[index].id!, - )) { - viewModel.getTenderApprovals(widget.tenders[index].id!); + if (!viewModel.hasTenderApprovalForTender(id)) { + viewModel.getTenderApprovals(id); } - // Check if we need to load more tenders (when user reaches near the end) if (index >= widget.tenders.length - 1 && viewModel.hasMoreData && !viewModel.isLoadingMore) { diff --git a/lib/views/tenders/widgets/tender_action_buttons_row.dart b/lib/views/tenders/widgets/tender_action_buttons_row.dart index 02f56b9..f7ee40f 100644 --- a/lib/views/tenders/widgets/tender_action_buttons_row.dart +++ b/lib/views/tenders/widgets/tender_action_buttons_row.dart @@ -35,13 +35,13 @@ class TenderActionButtonsRow extends StatelessWidget { // Dislike button _dislikeButton(viewModel), - SizedBox(width: 32.0.w()), + isDesktop ? Container() : SizedBox(width: 32.0.w()), //reject button - _rejectButton(viewModel), - SizedBox(width: 32.0.w()), + isDesktop ? Container() : _rejectButton(viewModel), + isDesktop ? Container() : SizedBox(width: 32.0.w()), //submit button - _submitButton(viewModel, context), - SizedBox(width: 32.0.w()), + isDesktop ? Container() : _submitButton(viewModel, context), + SizedBox(width: isDesktop ? 16.0.h() : 32.0.w()), // Like button _likeButton(viewModel), ], diff --git a/lib/views/tenders/widgets/tender_card.dart b/lib/views/tenders/widgets/tender_card.dart index 5c76bc1..b08424a 100644 --- a/lib/views/tenders/widgets/tender_card.dart +++ b/lib/views/tenders/widgets/tender_card.dart @@ -26,7 +26,7 @@ class TenderCard extends StatelessWidget { Container( width: double.infinity, margin: EdgeInsets.symmetric(horizontal: 16.0.w()), - height: 587.0.h(), + height: isDesktop ? 290.0.h() : 587.0.h(), decoration: BoxDecoration( color: AppColors.grey0, borderRadius: BorderRadius.circular(12), @@ -56,38 +56,41 @@ class TenderCard extends StatelessWidget { // Deadline badge _deadlineBadge(), - SizedBox(height: 12.0.h()), + isDesktop ? Container() : SizedBox(height: 12.0.h()), // Approval status badge // _approvalStatusBadge(), - SizedBox(height: 12.0.h()), + SizedBox(height: isDesktop ? 6.0.h() : 12.0.h()), // Title _tenderTitle(), - SizedBox(height: 8.0.h()), + SizedBox(height: isDesktop ? 4.0.h() : 8.0.h()), // Description _tenderDescription(), - SizedBox(height: 12.0.h()), + isDesktop ? Container() : SizedBox(height: 12.0.h()), // Tender ID - _idText(), + isDesktop ? Container() : _idText(), ], ), ), const Spacer(), // Bottom section with progress and actions Padding( - padding: EdgeInsets.all(16.0.w()), + padding: EdgeInsets.symmetric( + vertical: isDesktop ? 8.0.w() : 16.0.w(), + horizontal: 16.0.w(), + ), child: Column( children: [ // Match percentage _matchPercentage(), - SizedBox(height: 8.0.h()), + SizedBox(height: isDesktop ? 4.0.h() : 8.0.h()), // Progress bar _progressBar(), - SizedBox(height: 16.0.h()), + SizedBox(height: isDesktop ? 8.0.h() : 16.0.h()), // Location and apply button Row( @@ -105,7 +108,7 @@ class TenderCard extends StatelessWidget { ], ), ), - SizedBox(height: 72.0.h()), + SizedBox(height: isDesktop ? 20.0.h() : 72.0.h()), if (!isDesktop) TenderActionButtonsRow(tender: tender), ], ); @@ -158,10 +161,11 @@ class TenderCard extends StatelessWidget { Widget _tenderTitle() { return Text( tender.title ?? '', + maxLines: isDesktop ? 2 : 4, style: TextStyle( color: AppColors.grey80, fontSize: 16.0.sp(), - fontWeight: FontWeight.w600, + fontWeight: isDesktop ? FontWeight.w500 : FontWeight.w600, ), ); } @@ -169,11 +173,11 @@ class TenderCard extends StatelessWidget { Widget _tenderDescription() { return Text( tender.description ?? '', - maxLines: 4, + maxLines: isDesktop ? 2 : 4, overflow: TextOverflow.ellipsis, style: TextStyle( color: AppColors.grey70, - fontSize: 16.0.sp(), + fontSize: isDesktop ? 14.0.sp() : 16.0.sp(), fontWeight: FontWeight.w400, height: 1.5, ), @@ -199,15 +203,15 @@ class TenderCard extends StatelessWidget { TenderDetailsStrings.tenderMatchProfile, style: TextStyle( color: AppColors.grey80, - fontSize: 14.0.sp(), - fontWeight: FontWeight.w500, + fontSize: isDesktop ? 12.0.sp() : 14.0.sp(), + fontWeight: isDesktop ? FontWeight.w400 : FontWeight.w500, ), ), Text( '${45}%', style: TextStyle( color: AppColors.secondaryTextColor, - fontSize: 16.0.sp(), + fontSize: isDesktop ? 12.0.sp() : 16.0.sp(), fontWeight: FontWeight.w600, ), ),