From bcd499843bca6d1f3dbc1689de032daf82ce5190 Mon Sep 17 00:00:00 2001 From: amirrezaghabeli Date: Wed, 27 Aug 2025 13:03:09 +0330 Subject: [PATCH 1/7] fixed services --- lib/core/network/network_manager.dart | 4 +- lib/core/utils/date_utils.dart | 24 ++++++++--- lib/data/repositories/auth_repository.dart | 2 +- lib/data/repositories/home_repository.dart | 8 ---- lib/data/repositories/tenders_repository.dart | 2 +- lib/data/services/api/auth_api.dart | 4 ++ lib/data/services/api/home_api.dart | 13 ++++++ lib/data/services/api/profile_api.dart | 4 ++ lib/data/services/api/tender_detail_api.dart | 6 +++ lib/data/services/api/tenders_api.dart | 20 ++++++++++ lib/data/services/api/your_tenders_api.dart | 4 ++ lib/data/services/auth_service.dart | 7 +++- lib/data/services/home_service.dart | 40 +++---------------- lib/data/services/profile_service.dart | 5 ++- lib/data/services/tender_detail_service.dart | 3 +- lib/data/services/tenders_service.dart | 13 +++--- lib/data/services/your_tenders_service.dart | 26 ++++-------- lib/view_models/home_view_model.dart | 27 ++----------- lib/view_models/tenders_view_model.dart | 4 +- .../detail/widgets/tender_detail_header.dart | 4 +- 20 files changed, 112 insertions(+), 108 deletions(-) create mode 100644 lib/data/services/api/auth_api.dart create mode 100644 lib/data/services/api/home_api.dart create mode 100644 lib/data/services/api/profile_api.dart create mode 100644 lib/data/services/api/tender_detail_api.dart create mode 100644 lib/data/services/api/tenders_api.dart create mode 100644 lib/data/services/api/your_tenders_api.dart diff --git a/lib/core/network/network_manager.dart b/lib/core/network/network_manager.dart index 9832ccb..5f96cab 100644 --- a/lib/core/network/network_manager.dart +++ b/lib/core/network/network_manager.dart @@ -32,7 +32,9 @@ class NetworkManager { } void _addInterceptors() { - if (_interceptorsAdded) return; + if (_interceptorsAdded) { + return; + } final requestInterceptor = InterceptorsWrapper( onRequest: (options, handler) { diff --git a/lib/core/utils/date_utils.dart b/lib/core/utils/date_utils.dart index 13eb8ab..aabdfa9 100644 --- a/lib/core/utils/date_utils.dart +++ b/lib/core/utils/date_utils.dart @@ -3,7 +3,9 @@ import 'package:intl/intl.dart'; class DateUtils { /// Converts Unix timestamp (seconds since epoch) to a formatted date string static String unixToDate(int? unixTimestamp, {String format = 'yyyy-MM-dd'}) { - if (unixTimestamp == null) return ''; + if (unixTimestamp == null) { + return ''; + } // Convert seconds to milliseconds if needed int timestampInMs = @@ -17,7 +19,9 @@ class DateUtils { /// Converts Unix timestamp to a readable date string static String unixToReadableDate(int? unixTimestamp) { - if (unixTimestamp == null) return ''; + if (unixTimestamp == null) { + return ''; + } int timestampInMs = unixTimestamp > 1000000000000 ? unixTimestamp : unixTimestamp * 1000; @@ -41,7 +45,9 @@ class DateUtils { /// Converts Unix timestamp to a short date format static String unixToShortDate(int? unixTimestamp) { - if (unixTimestamp == null) return ''; + if (unixTimestamp == null) { + return ''; + } int timestampInMs = unixTimestamp > 1000000000000 ? unixTimestamp : unixTimestamp * 1000; @@ -54,7 +60,9 @@ class DateUtils { /// Converts Unix timestamp to a full date and time format static String unixToDateTime(int? unixTimestamp) { - if (unixTimestamp == null) return ''; + if (unixTimestamp == null) { + return ''; + } int timestampInMs = unixTimestamp > 1000000000000 ? unixTimestamp : unixTimestamp * 1000; @@ -67,7 +75,9 @@ class DateUtils { /// Checks if a Unix timestamp is in the past static bool isPast(int? unixTimestamp) { - if (unixTimestamp == null) return false; + if (unixTimestamp == null) { + return false; + } int timestampInMs = unixTimestamp > 1000000000000 ? unixTimestamp : unixTimestamp * 1000; @@ -80,7 +90,9 @@ class DateUtils { /// Gets the number of days remaining until a Unix timestamp static int daysRemaining(int? unixTimestamp) { - if (unixTimestamp == null) return 0; + if (unixTimestamp == null) { + return 0; + } int timestampInMs = unixTimestamp > 1000000000000 ? unixTimestamp : unixTimestamp * 1000; diff --git a/lib/data/repositories/auth_repository.dart b/lib/data/repositories/auth_repository.dart index 76acdbb..a931d05 100644 --- a/lib/data/repositories/auth_repository.dart +++ b/lib/data/repositories/auth_repository.dart @@ -1,8 +1,8 @@ import 'package:tm_app/data/services/model/login_response/login_response_model.dart'; -import 'package:tm_app/data/services/model/logout_response/logout_response.dart'; import '../../core/utils/result.dart'; import '../services/auth_service.dart'; +import '../services/model/logout_response/logout_response.dart'; class AuthRepository { final AuthService _authService; diff --git a/lib/data/repositories/home_repository.dart b/lib/data/repositories/home_repository.dart index 2ba1740..3ba476e 100644 --- a/lib/data/repositories/home_repository.dart +++ b/lib/data/repositories/home_repository.dart @@ -1,7 +1,5 @@ import 'package:tm_app/core/utils/result.dart'; import 'package:tm_app/data/services/home_service.dart'; -import 'package:tm_app/data/services/model/home/home_request/home_request_model.dart'; -import 'package:tm_app/data/services/model/home/home_response/home_response_model.dart'; import 'package:tm_app/data/services/model/recommended_tenders_response/recommended_tenders_response.dart'; import 'package:tm_app/data/services/model/tender_approvals_response/tender_approvals_response.dart'; import 'package:tm_app/data/services/model/tender_approvals_state_response/tender_approvals_state_response.dart'; @@ -13,12 +11,6 @@ class HomeRepository { : _homeService = homeService; final HomeService _homeService; - Future> getHome({ - HomeRequestModel? homeRequestModel, - }) async { - return _homeService.getHome(homeRequestModel: homeRequestModel); - } - Future> getHomeRecommendedTenders({ required int limit, required int offset, diff --git a/lib/data/repositories/tenders_repository.dart b/lib/data/repositories/tenders_repository.dart index 8d991ad..a54a1f5 100644 --- a/lib/data/repositories/tenders_repository.dart +++ b/lib/data/repositories/tenders_repository.dart @@ -28,7 +28,7 @@ class TendersRepository { required String tenderId, required String feedbackType, }) async { - return _tendersService.feedback( + return _tendersService.toggleFeedback( tenderId: tenderId, feedbackType: feedbackType, ); diff --git a/lib/data/services/api/auth_api.dart b/lib/data/services/api/auth_api.dart new file mode 100644 index 0000000..bc2a7ba --- /dev/null +++ b/lib/data/services/api/auth_api.dart @@ -0,0 +1,4 @@ +class AuthApi { + static const String login = '/api/v1/profile/login'; + static const String logout = '/api/v1/profile/logout'; +} diff --git a/lib/data/services/api/home_api.dart b/lib/data/services/api/home_api.dart new file mode 100644 index 0000000..c2cab7d --- /dev/null +++ b/lib/data/services/api/home_api.dart @@ -0,0 +1,13 @@ +class HomeApi { + static const String recommendedTenders = '/api/v1/tenders/recommend'; + static String getRecommendedTenders({ + required int limit, + required int offset, + }) { + return '$recommendedTenders?limit=$limit&offset=$offset'; + } + + static const String tenderApprovals = '/api/v1/tender-approvals'; + static const String tenderApprovalStats = '/api/v1/tender-approvals/stats'; + static const String statsCompany = '/api/v1/feedback/stats/company'; +} diff --git a/lib/data/services/api/profile_api.dart b/lib/data/services/api/profile_api.dart new file mode 100644 index 0000000..c6e2336 --- /dev/null +++ b/lib/data/services/api/profile_api.dart @@ -0,0 +1,4 @@ +class ProfileApi { + static const String profile = '/api/v1/profile'; + static const String companyProfile = '/api/v1/companies'; +} diff --git a/lib/data/services/api/tender_detail_api.dart b/lib/data/services/api/tender_detail_api.dart new file mode 100644 index 0000000..90eae52 --- /dev/null +++ b/lib/data/services/api/tender_detail_api.dart @@ -0,0 +1,6 @@ +class TenderDetailApi { + static const String tenderDetails = '/api/v1/tenders/details'; + static String getTenderDetails({required String tenderId}) { + return '$tenderDetails/$tenderId'; + } +} diff --git a/lib/data/services/api/tenders_api.dart b/lib/data/services/api/tenders_api.dart new file mode 100644 index 0000000..facce5d --- /dev/null +++ b/lib/data/services/api/tenders_api.dart @@ -0,0 +1,20 @@ +class TendersApi { + static const String tenders = '/api/v1/tenders'; + static String getTenders({required int limit, required int offset}) { + return '$tenders?limit=$limit&offset=$offset'; + } + + static const String feedbackTender = '/api/v1/feedback/tender'; + static String getFeedbackForTender({required String tenderId}) { + return '$feedbackTender/$tenderId'; + } + + static const String feedback = '/api/v1/feedback'; + + static const String tenderApprovals = '/api/v1/tender-approvals'; + + static const String tenderApprovalsTender = '/api/v1/tender-approvals/tender'; + static String getTenderApprovalById({required String tenderId}) { + return '$tenderApprovalsTender/$tenderId'; + } +} diff --git a/lib/data/services/api/your_tenders_api.dart b/lib/data/services/api/your_tenders_api.dart new file mode 100644 index 0000000..99ad134 --- /dev/null +++ b/lib/data/services/api/your_tenders_api.dart @@ -0,0 +1,4 @@ +class YourTendersApi { + static const String tenderApprovalsStatus = + '/api/v1/tender-approvals?status=submitted'; +} diff --git a/lib/data/services/auth_service.dart b/lib/data/services/auth_service.dart index 9b529d6..66ef13c 100644 --- a/lib/data/services/auth_service.dart +++ b/lib/data/services/auth_service.dart @@ -3,6 +3,8 @@ import 'dart:convert'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:tm_app/core/utils/logger.dart'; +import 'package:tm_app/data/services/api/auth_api.dart'; import 'package:tm_app/data/services/model/login_response/login_response_model.dart'; import 'package:tm_app/data/services/model/logout_response/logout_response.dart'; @@ -22,7 +24,7 @@ class AuthService { var data = jsonEncode({"password": password, "username": username}); final result = await _networkManager.makeRequest( - '/api/v1/profile/login', + AuthApi.login, method: 'POST', (json) => LoginResponseModel.fromJson(json), data: data, @@ -41,7 +43,7 @@ class AuthService { Future> logout() async { final result = await _networkManager.makeRequest( - '/api/v1/profile/logout', + AuthApi.logout, method: 'DELETE', (json) => LogoutResponse.fromJson(json), ); @@ -52,5 +54,6 @@ class AuthService { final prefs = await SharedPreferences.getInstance(); await prefs.remove('bearer'); await prefs.remove('refresh_token'); + appLogger.info('tokens cleared!'); } } diff --git a/lib/data/services/home_service.dart b/lib/data/services/home_service.dart index d3f6658..76f9c71 100644 --- a/lib/data/services/home_service.dart +++ b/lib/data/services/home_service.dart @@ -1,12 +1,6 @@ -import 'dart:convert'; - -import 'package:dio/dio.dart'; -import 'package:tm_app/core/utils/logger.dart'; import 'package:tm_app/core/utils/result.dart'; -import 'package:tm_app/data/services/mock_data/home_mock_data.dart'; +import 'package:tm_app/data/services/api/home_api.dart'; import 'package:tm_app/data/services/model/feedback_stats_response/feedback_stat_response.dart'; -import 'package:tm_app/data/services/model/home/home_request/home_request_model.dart'; -import 'package:tm_app/data/services/model/home/home_response/home_response_model.dart'; import 'package:tm_app/data/services/model/recommended_tenders_response/recommended_tenders_response.dart'; import 'package:tm_app/data/services/model/tender_approvals_response/tender_approvals_response.dart'; import 'package:tm_app/data/services/model/tender_approvals_state_response/tender_approvals_state_response.dart'; @@ -18,36 +12,12 @@ class HomeService { : _networkManager = networkManager; final NetworkManager _networkManager; - Future> getHome({ - HomeRequestModel? homeRequestModel, - }) async { - try { - // final result = await _networkManager.makeRequest( - // '/api/v1/home', - // (json) => HomeResponseModel.fromJson(json), - // method: 'GET', - // ); - await Future.delayed(Duration(seconds: 2)); - appLogger.info('get home success'); - // TODO: will change when api is ready - return Result.ok( - HomeResponseModel.fromJson(jsonDecode(homeMockData)['data']), - ); - } on DioException catch (e) { - appLogger.error('get home failed: $e'); - return Result.error(e); - } on Exception catch (e, stackTrace) { - appLogger.error('get home failed: $e', stackTrace: stackTrace); - return Result.error(Exception(e)); - } - } - Future> getRecommendedTenders({ required int limit, required int offset, }) async { final result = await _networkManager.makeRequest( - '/api/v1/tenders/recommend?limit=$limit&offset=$offset', + HomeApi.getRecommendedTenders(limit: limit, offset: offset), method: 'GET', (json) => RecommendedTendersResponse.fromJson(json), ); @@ -56,7 +26,7 @@ class HomeService { Future> getYourTenders() async { final result = await _networkManager.makeRequest( - '/api/v1/tender-approvals', + HomeApi.tenderApprovals, (json) => TenderApprovalsResponse.fromJson(json), method: 'GET', ); @@ -67,7 +37,7 @@ class HomeService { Future> getTendersApprovalsState() async { final result = await _networkManager.makeRequest( - '/api/v1/tender-approvals/stats', + HomeApi.tenderApprovalStats, (json) => TenderApprovalsStateResponse.fromJson(json), method: 'GET', ); @@ -77,7 +47,7 @@ class HomeService { Future> getFeedbackStats() async { final result = await _networkManager.makeRequest( - '/api/v1/feedback/stats/company', + HomeApi.statsCompany, (json) => FeedbackStatResponse.fromJson(json), method: 'GET', ); diff --git a/lib/data/services/profile_service.dart b/lib/data/services/profile_service.dart index 58012d5..8e52722 100644 --- a/lib/data/services/profile_service.dart +++ b/lib/data/services/profile_service.dart @@ -1,3 +1,4 @@ +import 'package:tm_app/data/services/api/profile_api.dart'; import 'package:tm_app/data/services/model/company_profile_response/company_profile_response.dart'; import 'package:tm_app/data/services/model/profile_response/profile_response.dart'; @@ -12,7 +13,7 @@ class ProfileService { Future> getProfile() async { final result = await _networkManager.makeRequest( - '/api/v1/profile', + ProfileApi.profile, method: 'GET', (json) => ProfileResponse.fromJson(json), ); @@ -21,7 +22,7 @@ class ProfileService { Future> getCompanyProfile() async { final result = await _networkManager.makeRequest( - '/api/v1/companies', + ProfileApi.companyProfile, method: 'GET', (json) => CompanyProfileResponse.fromJson(json), ); diff --git a/lib/data/services/tender_detail_service.dart b/lib/data/services/tender_detail_service.dart index 4f51543..ab7664b 100644 --- a/lib/data/services/tender_detail_service.dart +++ b/lib/data/services/tender_detail_service.dart @@ -1,4 +1,5 @@ import 'package:tm_app/core/utils/result.dart'; +import 'package:tm_app/data/services/api/tender_detail_api.dart'; import 'package:tm_app/data/services/model/tender_detail_response/tender_detail_response_model.dart'; import '../../core/network/network_manager.dart'; @@ -13,7 +14,7 @@ class TenderDetailService { required String tenderId, }) async { final result = await _networkManager.makeRequest( - '/api/v1/tenders/details/$tenderId', + TenderDetailApi.getTenderDetails(tenderId: tenderId), method: 'GET', (json) => TenderDetailResponseModel.fromJson(json), ); diff --git a/lib/data/services/tenders_service.dart b/lib/data/services/tenders_service.dart index 709c398..216045f 100644 --- a/lib/data/services/tenders_service.dart +++ b/lib/data/services/tenders_service.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:tm_app/core/utils/result.dart'; +import 'package:tm_app/data/services/api/tenders_api.dart'; import 'package:tm_app/data/services/model/feedback_response/feedback_response.dart'; import 'package:tm_app/data/services/model/tender_approvals_by_id_response/tender_approvals_by_id_response.dart'; import 'package:tm_app/data/services/model/tender_approvals_toggle_response/tender_approvals_toggle_response.dart'; @@ -18,7 +19,7 @@ class TendersService { required int offset, }) async { final result = await _networkManager.makeRequest( - '/api/v1/tenders?limit=$limit&offset=$offset', + TendersApi.getTenders(limit: limit, offset: offset), method: 'GET', (json) => TendersResponse.fromJson(json), ); @@ -29,14 +30,14 @@ class TendersService { required String tenderId, }) async { final result = await _networkManager.makeRequest( - '/api/v1/feedback/tender/$tenderId', + TendersApi.getFeedbackForTender(tenderId: tenderId), method: 'GET', (json) => FeedbackResponse.fromJson(json), ); return result; } - Future> feedback({ + Future> toggleFeedback({ required String tenderId, required String feedbackType, }) async { @@ -45,7 +46,7 @@ class TendersService { 'tender_id': tenderId, }); final result = await _networkManager.makeRequest( - '/api/v1/feedback', + TendersApi.feedback, method: 'POST', (json) => FeedbackResponse.fromJson(json), data: data, @@ -64,7 +65,7 @@ class TendersService { 'status': status, }); final result = await _networkManager.makeRequest( - '/api/v1/tender-approvals', + TendersApi.tenderApprovals, method: 'POST', (json) => TenderApprovalsToggleResponse.fromJson(json), data: data, @@ -76,7 +77,7 @@ class TendersService { required String tenderId, }) async { final result = await _networkManager.makeRequest( - '/api/v1/tender-approvals/tender/$tenderId', + TendersApi.getTenderApprovalById(tenderId: tenderId), method: 'GET', (json) => TenderApprovalsByIdResponse.fromJson(json), ); diff --git a/lib/data/services/your_tenders_service.dart b/lib/data/services/your_tenders_service.dart index c7ec695..bb36bbe 100644 --- a/lib/data/services/your_tenders_service.dart +++ b/lib/data/services/your_tenders_service.dart @@ -1,6 +1,5 @@ -import 'package:dio/dio.dart'; -import 'package:tm_app/core/utils/logger.dart'; import 'package:tm_app/core/utils/result.dart'; +import 'package:tm_app/data/services/api/your_tenders_api.dart'; import 'package:tm_app/data/services/model/tender_approvals_response/tender_approvals_response.dart'; import '../../../core/network/network_manager.dart'; @@ -12,23 +11,12 @@ class YourTendersService { final NetworkManager _networkManager; Future> getApprovedTenders() async { - try { - final result = await _networkManager.makeRequest( - '/api/v1/tender-approvals?status=submitted', - (json) => TenderApprovalsResponse.fromJson(json), - method: 'GET', - ); + final result = await _networkManager.makeRequest( + YourTendersApi.tenderApprovalsStatus, + (json) => TenderApprovalsResponse.fromJson(json), + method: 'GET', + ); - return result; - } on DioException catch (e) { - appLogger.error('get submitted tenders failed: $e'); - return Result.error(e); - } on Exception catch (e, stackTrace) { - appLogger.error( - 'get submitted tenders failed: $e', - stackTrace: stackTrace, - ); - return Result.error(Exception(e)); - } + return result; } } diff --git a/lib/view_models/home_view_model.dart b/lib/view_models/home_view_model.dart index 0f2fa3a..cc947f2 100644 --- a/lib/view_models/home_view_model.dart +++ b/lib/view_models/home_view_model.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:tm_app/core/utils/result.dart'; import 'package:tm_app/data/services/model/feedback_stats/feedback_stats.dart'; -import 'package:tm_app/data/services/model/home/home_request/home_request_model.dart'; import 'package:tm_app/data/services/model/recommended_tenders_response/recommended_tenders_response.dart'; import 'package:tm_app/data/services/model/tender_approvals_response/tender_approvals_response.dart'; import 'package:tm_app/data/services/model/tender_approvals_state_response/tender_approvals_state_response.dart'; @@ -51,30 +50,10 @@ class HomeViewModel with ChangeNotifier { await getFeedbackStats(); } - Future getHome({HomeRequestModel? homeRequestModel}) async { - _isLoading = true; - _errorMessage = null; - notifyListeners(); - - final result = await _homeRepository.getHome( - homeRequestModel: homeRequestModel, - ); - - switch (result) { - case Ok(): - _homeResponse = result.value; - break; - case Error(): - _errorMessage = result.error.toString(); - break; - } - - _isLoading = false; - notifyListeners(); - } - Future getHomeRecommandTenders({bool reset = false}) async { - if (_isLoadingMore || (!_hasMore && !reset)) return; + if (_isLoadingMore || (!_hasMore && !reset)) { + return; + } if (reset) { _currentPage = 0; diff --git a/lib/view_models/tenders_view_model.dart b/lib/view_models/tenders_view_model.dart index c954e8f..af0a25a 100644 --- a/lib/view_models/tenders_view_model.dart +++ b/lib/view_models/tenders_view_model.dart @@ -122,7 +122,9 @@ class TendersViewModel with ChangeNotifier { } Future loadMoreTenders() async { - if (_isLoadingMore || !_hasMoreData) return; + if (_isLoadingMore || !_hasMoreData) { + return; + } _isLoadingMore = true; _errorMessage = null; diff --git a/lib/views/detail/widgets/tender_detail_header.dart b/lib/views/detail/widgets/tender_detail_header.dart index a216068..b38abab 100644 --- a/lib/views/detail/widgets/tender_detail_header.dart +++ b/lib/views/detail/widgets/tender_detail_header.dart @@ -75,7 +75,9 @@ class TenderDetailHeader extends StatelessWidget { } String unixToDate(int? unixTimestamp, {String format = 'yyyy-MM-dd'}) { - if (unixTimestamp == null) return ''; + if (unixTimestamp == null) { + return ''; + } // Convert seconds to milliseconds if needed int timestampInMs = From dd46eb605465bf933e72ab8d1c1b150329b6c3b0 Mon Sep 17 00:00:00 2001 From: amirrezaghabeli Date: Wed, 27 Aug 2025 13:22:35 +0330 Subject: [PATCH 2/7] refactor home --- .../routes/app_shell/app_shell_screen.dart | 3 + lib/core/theme/colors.dart | 1 + lib/data/repositories/tenders_repository.dart | 2 +- lib/view_models/home_view_model.dart | 23 +++ lib/view_models/tenders_view_model.dart | 4 +- lib/views/home/pages/d_home_page.dart | 159 ++++++++---------- lib/views/home/pages/m_home_page.dart | 43 ++--- lib/views/home/pages/t_home_page.dart | 106 +++++------- lib/views/home/progress_bar_column.dart | 2 +- lib/views/home/tender_card.dart | 3 +- lib/views/home/tenders_list_item.dart | 18 -- .../widgets/tender_action_buttons_row.dart | 7 +- 12 files changed, 169 insertions(+), 202 deletions(-) diff --git a/lib/core/routes/app_shell/app_shell_screen.dart b/lib/core/routes/app_shell/app_shell_screen.dart index d157f03..54b561f 100644 --- a/lib/core/routes/app_shell/app_shell_screen.dart +++ b/lib/core/routes/app_shell/app_shell_screen.dart @@ -13,6 +13,9 @@ class AppShellScreen extends StatelessWidget { final StatefulNavigationShell navigationShell; void _gotoBranch(int index, BuildContext context) { + if (index == navigationShell.currentIndex) { + return; + } navigationShell.goBranch( index, initialLocation: index == navigationShell.currentIndex, diff --git a/lib/core/theme/colors.dart b/lib/core/theme/colors.dart index 4407c30..0d87b8e 100644 --- a/lib/core/theme/colors.dart +++ b/lib/core/theme/colors.dart @@ -153,6 +153,7 @@ class AppColors { // static const Color red0 = Color(0xFF312F2F); // static const Color red10 = Color(0xFF524545); static const Color red = Color(0xFFFF3B30); + static const Color orange = Color(0xFFE5821E); // Other colors static const Color lightBlue = Color(0xFFe8ecfc); diff --git a/lib/data/repositories/tenders_repository.dart b/lib/data/repositories/tenders_repository.dart index a54a1f5..ed366af 100644 --- a/lib/data/repositories/tenders_repository.dart +++ b/lib/data/repositories/tenders_repository.dart @@ -24,7 +24,7 @@ class TendersRepository { return _tendersService.getTenderFeedback(tenderId: tenderId); } - Future> feedback({ + Future> toggleFeedback({ required String tenderId, required String feedbackType, }) async { diff --git a/lib/view_models/home_view_model.dart b/lib/view_models/home_view_model.dart index cc947f2..0eba9d9 100644 --- a/lib/view_models/home_view_model.dart +++ b/lib/view_models/home_view_model.dart @@ -165,4 +165,27 @@ class HomeViewModel with ChangeNotifier { _isLoading = false; notifyListeners(); } + + // Calculation methods for progress bars + double get partnershipCount { + return tenderApprovalsStateResponse?.data?.partnershipCount?.toDouble() ?? + 0.0; + } + + double get selfApplyCount { + return tenderApprovalsStateResponse?.data?.selfApplyCount?.toDouble() ?? + 0.0; + } + + double get totalCount { + return partnershipCount + selfApplyCount; + } + + double get partnershipPercent { + return totalCount > 0 ? (partnershipCount / totalCount) * 100 : 0; + } + + double get selfApplyPercent { + return totalCount > 0 ? (selfApplyCount / totalCount) * 100 : 0; + } } diff --git a/lib/view_models/tenders_view_model.dart b/lib/view_models/tenders_view_model.dart index af0a25a..24d6971 100644 --- a/lib/view_models/tenders_view_model.dart +++ b/lib/view_models/tenders_view_model.dart @@ -248,14 +248,14 @@ class TendersViewModel with ChangeNotifier { notifyListeners(); } - Future feedback({ + Future toogleFeedback({ required String tenderId, required String feedbackType, }) async { _feedbackErrorMessage = null; notifyListeners(); - final result = await _tendersRepository.feedback( + final result = await _tendersRepository.toggleFeedback( tenderId: tenderId, feedbackType: feedbackType, ); diff --git a/lib/views/home/pages/d_home_page.dart b/lib/views/home/pages/d_home_page.dart index f89dfd1..9c79fdc 100644 --- a/lib/views/home/pages/d_home_page.dart +++ b/lib/views/home/pages/d_home_page.dart @@ -6,7 +6,6 @@ import 'package:tm_app/core/constants/strings.dart'; import 'package:tm_app/core/routes/app_routes.dart'; import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/core/utils/size_config.dart'; -import 'package:tm_app/data/services/model/tender_approvals_state_response/tender_approvals_state_response.dart'; import 'package:tm_app/view_models/home_view_model.dart'; import 'package:tm_app/views/shared/desktop_navigation_widget.dart'; @@ -30,111 +29,99 @@ class DesktopHomePage extends StatelessWidget { return Center(child: Text(homeViewModel.errorMessage!)); } - return Column( - children: [ - DesktopNavigationWidget( - currentIndex: 0, // Home index - onTabChanged: (index) { - // Navigate to different screens based on index - switch (index) { - case 0: - // Already on home - break; - case 1: - // Navigate to tenders - Router.neglect(context, () => context.go('/tenders')); - break; - case 2: - // Navigate to profile - Router.neglect(context, () => context.go('/profile')); - break; - } - }, - ), - Expanded( - child: Center( - child: SizedBox( - width: 780, - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox(height: 55), - // SizedBox(width: 780, child: NotificationCard()), - SizedBox(height: 40.0), - _progressBarsRow( - homeViewModel.tenderApprovalsStateResponse!, - ), - SizedBox(height: 32.0.h()), - Padding( - padding: EdgeInsets.symmetric(horizontal: 15.0.w()), - child: _firstTenderCardsRow( - context, - homeViewModel.tenderApprovalsStateResponse!, - homeViewModel, + if (homeViewModel.tenderApprovalsStateResponse != null && + homeViewModel.data != null && + homeViewModel.feedbackStats != null) { + return Column( + children: [ + DesktopNavigationWidget( + currentIndex: 0, // Home index + onTabChanged: (index) { + // Navigate to different screens based on index + switch (index) { + case 0: + // Already on home + break; + case 1: + // Navigate to tenders + Router.neglect(context, () => context.go('/tenders')); + break; + case 2: + // Navigate to profile + Router.neglect(context, () => context.go('/profile')); + break; + } + }, + ), + Expanded( + child: Center( + child: SizedBox( + width: 780, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(height: 55), + // SizedBox(width: 780, child: NotificationCard()), + SizedBox(height: 40.0), + _progressBarsRow(homeViewModel), + SizedBox(height: 32.0.h()), + Padding( + padding: EdgeInsets.symmetric( + horizontal: 15.0.w(), + ), + child: _firstTenderCardsRow( + context, + homeViewModel, + ), ), - ), - SizedBox(height: 32.0.h()), - _yourTenderText(homeViewModel), - SizedBox(height: 8.0.h()), - _bottomListView(homeViewModel), - ], + SizedBox(height: 32.0.h()), + _yourTenderText(homeViewModel), + SizedBox(height: 8.0.h()), + _bottomListView(homeViewModel), + ], + ), ), ), ), ), - ), - ], + ], + ); + } + return const Center( + child: CircularProgressIndicator(color: AppColors.secondary50), ); }, ), ); } - Widget _progressBarsRow( - TenderApprovalsStateResponse tenderApprovalsStateResponse, - ) { - final partnership = - tenderApprovalsStateResponse.data!.partnershipCount ?? 0; - final selfApply = tenderApprovalsStateResponse.data!.selfApplyCount ?? 0; - final total = (partnership + selfApply); - - final partnershipPercent = total > 0 ? (partnership / total) * 100 : 0; - final selfApplyPercent = total > 0 ? (selfApply / total) * 100 : 0; + Widget _progressBarsRow(HomeViewModel homeViewModel) { return Row( // mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.center, children: [ ProgressBarColumn( text: AppStrings.partnership, - value: double.parse('$partnershipPercent'), - amount: double.parse('$partnershipPercent').toString(), + value: homeViewModel.partnershipPercent, + amount: homeViewModel.partnershipPercent.toString(), circularProgressIndicatorWidth: 176, circularProgressIndicatorHeight: 176, ), SizedBox(width: 106), ProgressBarColumn( text: AppStrings.selfApply, - value: double.parse(selfApplyPercent.toString()), - amount: double.parse(selfApplyPercent.toString()).toString(), + value: homeViewModel.selfApplyPercent, + amount: homeViewModel.selfApplyPercent.toString(), circularProgressIndicatorWidth: 176, circularProgressIndicatorHeight: 176, ), - //SizedBox(width: 106), - // ProgressBarColumn( - // text: AppStrings.contracting, - // value: double.parse(homeResponse.contracting ?? '0'), - // amount: homeResponse.contracting ?? '0', - // circularProgressIndicatorWidth: 176, - // circularProgressIndicatorHeight: 176, - // ), ], ); } Widget _firstTenderCardsRow( BuildContext context, - TenderApprovalsStateResponse tenderApprovalsStateResponse, HomeViewModel homeViewModel, ) { return Row( @@ -143,16 +130,16 @@ class DesktopHomePage extends StatelessWidget { Expanded( child: TenderCard( backgroundColor: AppColors.primary20, - iconPath: 'assets/icons/arrows.svg', + iconPath: AssetsManager.arrows, title: AppStrings.tenderSubmitted, amount: - tenderApprovalsStateResponse.data!.submittedTenders.toString(), - // tenderApprovalsStateResponse - // .data! - // .approvalsBySubmission! - // .selfApply - // .toString(), - textColor: Color(0xFF0164FF), + homeViewModel + .tenderApprovalsStateResponse! + .data! + .submittedTenders + .toString(), + + textColor: AppColors.mainBlue, enableTap: true, width: 178, height: 148, @@ -168,7 +155,7 @@ class DesktopHomePage extends StatelessWidget { iconPath: AssetsManager.approve, title: AppStrings.approvedTenders, amount: '0', - textColor: Color(0xFF24848E), + textColor: AppColors.jellyBean, enableTap: true, width: 178, height: 148, @@ -180,10 +167,10 @@ class DesktopHomePage extends StatelessWidget { SizedBox(width: 10), TenderCard( backgroundColor: AppColors.orange10, - iconPath: 'assets/icons/shield.svg', + iconPath: AssetsManager.shield, title: AppStrings.tenderValue, amount: '3', - textColor: Color(0xFFE5821E), + textColor: AppColors.orange, enableTap: true, width: 178, height: 148, @@ -195,7 +182,7 @@ class DesktopHomePage extends StatelessWidget { iconPath: AssetsManager.thumbLike, title: AppStrings.likedTenders, amount: homeViewModel.feedbackStats?.totalLikes.toString() ?? '0', - textColor: Color(0xFF9E9E9E), + textColor: AppColors.grey80, enableTap: false, width: 178, height: 148, diff --git a/lib/views/home/pages/m_home_page.dart b/lib/views/home/pages/m_home_page.dart index 194c95e..0c41598 100644 --- a/lib/views/home/pages/m_home_page.dart +++ b/lib/views/home/pages/m_home_page.dart @@ -31,7 +31,8 @@ class MobileHomePage extends StatelessWidget { } if (homeViewModel.tenderApprovalsStateResponse != null && - homeViewModel.data != null) { + homeViewModel.data != null && + homeViewModel.feedbackStats != null) { return SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -39,7 +40,7 @@ class MobileHomePage extends StatelessWidget { // SizedBox(height: 18.0.h()), // NotificationCard(), SizedBox(height: 32.0.h()), - _progressBarsRow(homeViewModel.tenderApprovalsStateResponse!), + _progressBarsRow(homeViewModel), SizedBox(height: 32.0.h()), _firstTenderCardsRow( context, @@ -62,17 +63,7 @@ class MobileHomePage extends StatelessWidget { ); } - Widget _progressBarsRow( - TenderApprovalsStateResponse tenderApprovalsStateResponse, - ) { - final partnership = - tenderApprovalsStateResponse.data!.partnershipCount ?? 0; - final selfApply = tenderApprovalsStateResponse.data!.selfApplyCount ?? 0; - final total = (partnership + selfApply); - - final partnershipPercent = total > 0 ? (partnership / total) * 100 : 0; - final selfApplyPercent = total > 0 ? (selfApply / total) * 100 : 0; - + Widget _progressBarsRow(HomeViewModel homeViewModel) { return Padding( padding: EdgeInsets.symmetric(horizontal: 24.0.w()), child: Row( @@ -81,22 +72,17 @@ class MobileHomePage extends StatelessWidget { SizedBox(width: 20.0.w()), ProgressBarColumn( text: AppStrings.partnership, - value: double.parse('$partnershipPercent'), - amount: double.parse('$partnershipPercent').toString(), + value: homeViewModel.partnershipPercent, + amount: homeViewModel.partnershipPercent.toString(), ), Spacer(), ProgressBarColumn( text: AppStrings.selfApply, - value: double.parse(selfApplyPercent.toString()), - amount: double.parse(selfApplyPercent.toString()).toString(), + value: homeViewModel.selfApplyPercent, + amount: homeViewModel.selfApplyPercent.toString(), ), SizedBox(width: 20.0.w()), Spacer(), - // ProgressBarColumn( - // text: AppStrings.contracting, - // value: double.parse(tenderApprovalsStateResponse.data.contracting ?? '0'), - // amount: tenderApprovalsStateResponse.data.contracting ?? '0', - // ), ], ), ); @@ -117,12 +103,7 @@ class MobileHomePage extends StatelessWidget { title: AppStrings.tenderSubmitted, amount: tenderApprovalsStateResponse.data!.submittedTenders.toString(), - // tenderApprovalsStateResponse - // .data! - // .approvalsBySubmission! - // .selfApply - // .toString(), - textColor: Color(0xFF0164FF), + textColor: AppColors.mainBlue, enableTap: true, onTap: () { YourTendersRouteData().push(context); @@ -134,7 +115,7 @@ class MobileHomePage extends StatelessWidget { title: AppStrings.approvedTenders, amount: '0', - textColor: Color(0xFF24848E), + textColor: AppColors.jellyBean, enableTap: true, onTap: () { YourTendersRouteData().push(context); @@ -157,7 +138,7 @@ class MobileHomePage extends StatelessWidget { iconPath: AssetsManager.shield, title: AppStrings.tenderValue, amount: '3', - textColor: Color(0xFFE5821E), + textColor: AppColors.orange, enableTap: true, onTap: () {}, ), @@ -166,7 +147,7 @@ class MobileHomePage extends StatelessWidget { iconPath: AssetsManager.thumbLike, title: AppStrings.likedTenders, amount: homeViewModel.feedbackStats?.totalLikes.toString() ?? '0', - textColor: Color(0xFF9E9E9E), + textColor: AppColors.grey80, enableTap: false, onTap: () {}, ), diff --git a/lib/views/home/pages/t_home_page.dart b/lib/views/home/pages/t_home_page.dart index 38547e5..22aa5b4 100644 --- a/lib/views/home/pages/t_home_page.dart +++ b/lib/views/home/pages/t_home_page.dart @@ -81,58 +81,56 @@ class TabletHomePage extends StatelessWidget { return Center(child: Text(homeViewModel.errorMessage!)); } - return SingleChildScrollView( - child: Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 24.0.w(), - 24.0.h(), - 24.0.w(), - 24.0.h(), + if (homeViewModel.tenderApprovalsStateResponse != null && + homeViewModel.data != null && + homeViewModel.feedbackStats != null) { + return SingleChildScrollView( + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 24.0.w(), + 24.0.h(), + 24.0.w(), + 24.0.h(), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // NotificationCard(), + SizedBox(height: 40.0.h()), + _progressBarsRow(homeViewModel), + SizedBox(height: 32.0.h()), + _firstTenderCardsRow( + context, + homeViewModel.tenderApprovalsStateResponse!, + ), + SizedBox(height: 8.0.h()), + _secondTenderCardsRow(homeViewModel), + SizedBox(height: 32.0.h()), + _yourTenderText(homeViewModel), + SizedBox(height: 8.0.h()), + _bottomListView(homeViewModel), + ], + ), ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // NotificationCard(), - SizedBox(height: 40.0.h()), - _progressBarsRow(homeViewModel.tenderApprovalsStateResponse!), - SizedBox(height: 32.0.h()), - _firstTenderCardsRow( - context, - homeViewModel.tenderApprovalsStateResponse!, - ), - SizedBox(height: 8.0.h()), - _secondTenderCardsRow(homeViewModel), - SizedBox(height: 32.0.h()), - _yourTenderText(homeViewModel), - SizedBox(height: 8.0.h()), - _bottomListView(homeViewModel), - ], - ), - ), + ); + } + return const Center( + child: CircularProgressIndicator(color: AppColors.secondary50), ); }, ), ); } - Widget _progressBarsRow( - TenderApprovalsStateResponse tenderApprovalsStateResponse, - ) { - final partnership = - tenderApprovalsStateResponse.data!.partnershipCount ?? 0; - final selfApply = tenderApprovalsStateResponse.data!.selfApplyCount ?? 0; - final total = (partnership + selfApply); - - final partnershipPercent = total > 0 ? (partnership / total) * 100 : 0; - final selfApplyPercent = total > 0 ? (selfApply / total) * 100 : 0; + Widget _progressBarsRow(HomeViewModel homeViewModel) { return Row( children: [ Spacer(), SizedBox(width: 20.0.w()), ProgressBarColumn( text: AppStrings.partnership, - value: double.parse('$partnershipPercent'), - amount: double.parse('$partnershipPercent').toString(), + value: homeViewModel.partnershipPercent, + amount: homeViewModel.partnershipPercent.toString(), circularProgressIndicatorWidth: 176.0, circularProgressIndicatorHeight: 176.0, strokeWidth: 8.0, @@ -140,22 +138,14 @@ class TabletHomePage extends StatelessWidget { Spacer(), ProgressBarColumn( text: AppStrings.selfApply, - value: double.parse(selfApplyPercent.toString()), - amount: double.parse(selfApplyPercent.toString()).toString(), + value: homeViewModel.selfApplyPercent, + amount: homeViewModel.selfApplyPercent.toString(), circularProgressIndicatorWidth: 176.0, circularProgressIndicatorHeight: 176.0, strokeWidth: 8.0, ), SizedBox(width: 20.0.w()), Spacer(), - // ProgressBarColumn( - // text: AppStrings.contracting, - // value: double.parse(homeResponse.contracting ?? '0'), - // amount: homeResponse.contracting ?? '0', - // circularProgressIndicatorWidth: 176.0, - // circularProgressIndicatorHeight: 176.0, - // strokeWidth: 8.0, - // ), ], ); } @@ -170,16 +160,12 @@ class TabletHomePage extends StatelessWidget { Expanded( child: TenderCard( backgroundColor: AppColors.primary20, - iconPath: 'assets/icons/arrows.svg', + iconPath: AssetsManager.arrows, title: AppStrings.tenderSubmitted, amount: tenderApprovalsStateResponse.data!.submittedTenders.toString(), - // tenderApprovalsStateResponse - // .data! - // .approvalsBySubmission! - // .selfApply - // .toString(), - textColor: Color(0xFF0164FF), + + textColor: AppColors.mainBlue, enableTap: true, width: double.infinity, height: 148, @@ -196,7 +182,7 @@ class TabletHomePage extends StatelessWidget { title: AppStrings.approvedTenders, amount: '0', - textColor: Color(0xFF24848E), + textColor: AppColors.jellyBean, enableTap: true, width: double.infinity, height: 148, @@ -217,10 +203,10 @@ class TabletHomePage extends StatelessWidget { Expanded( child: TenderCard( backgroundColor: AppColors.orange10, - iconPath: 'assets/icons/shield.svg', + iconPath: AssetsManager.shield, title: AppStrings.tenderValue, amount: '3', - textColor: Color(0xFFE5821E), + textColor: AppColors.orange, enableTap: true, width: double.infinity, height: 148, @@ -234,7 +220,7 @@ class TabletHomePage extends StatelessWidget { iconPath: AssetsManager.thumbLike, title: AppStrings.likedTenders, amount: homeViewModel.feedbackStats?.totalLikes.toString() ?? '0', - textColor: Color(0xFF9E9E9E), + textColor: AppColors.grey80, enableTap: false, width: double.infinity, height: 148, diff --git a/lib/views/home/progress_bar_column.dart b/lib/views/home/progress_bar_column.dart index d57ec15..4a799a0 100644 --- a/lib/views/home/progress_bar_column.dart +++ b/lib/views/home/progress_bar_column.dart @@ -78,7 +78,7 @@ class _ProgressBarColumnState extends State animation: _animation, builder: (context, child) => CircularProgressIndicator( - backgroundColor: Color(0xFFE1E1E1), + backgroundColor: AppColors.grey10, strokeCap: StrokeCap.round, strokeWidth: widget.strokeWidth ?? 6.0, valueColor: AlwaysStoppedAnimation(AppColors.jellyBean), diff --git a/lib/views/home/tender_card.dart b/lib/views/home/tender_card.dart index 1b23f49..a9e1be1 100644 --- a/lib/views/home/tender_card.dart +++ b/lib/views/home/tender_card.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import '../../core/constants/assets.dart'; +import '../../core/theme/colors.dart'; import '../../core/utils/size_config.dart'; class TenderCard extends StatelessWidget { @@ -50,7 +51,7 @@ class TenderCard extends StatelessWidget { style: TextStyle( fontSize: 16.0.sp(), fontWeight: FontWeight.bold, - color: Color(0xFF777777), + color: AppColors.grey80, ), ), Spacer(), diff --git a/lib/views/home/tenders_list_item.dart b/lib/views/home/tenders_list_item.dart index e9f3d42..1a5fee9 100644 --- a/lib/views/home/tenders_list_item.dart +++ b/lib/views/home/tenders_list_item.dart @@ -97,7 +97,6 @@ class TendersListItem extends StatelessWidget { ), ), Spacer(), - // SizedBox(height: 18.0.h()), TenderCardProgressBar(), SizedBox(height: 15.0.h()), @@ -123,23 +122,6 @@ class TendersListItem extends StatelessWidget { ), ), Spacer(), - // Container( - // width: 96.0.w(), - // height: 24.0.h(), - // decoration: BoxDecoration( - // color: AppColors.grey30, - // borderRadius: BorderRadius.circular(88), - // ), - // alignment: Alignment.center, - // child: Text( - // 'tender.type', - // style: TextStyle( - // fontSize: 12.0.sp(), - // fontWeight: FontWeight.w500, - // color: AppColors.grey60, - // ), - // ), - // ), ], ), ], diff --git a/lib/views/tenders/widgets/tender_action_buttons_row.dart b/lib/views/tenders/widgets/tender_action_buttons_row.dart index f58dc5e..725a39e 100644 --- a/lib/views/tenders/widgets/tender_action_buttons_row.dart +++ b/lib/views/tenders/widgets/tender_action_buttons_row.dart @@ -207,7 +207,10 @@ class TenderActionButtonsRow extends StatelessWidget { child: InkWell( splashColor: Colors.transparent, onTap: () { - viewModel.feedback(tenderId: tender.id!, feedbackType: 'dislike'); + viewModel.toogleFeedback( + tenderId: tender.id!, + feedbackType: 'dislike', + ); }, child: Column( children: [ @@ -251,7 +254,7 @@ class TenderActionButtonsRow extends StatelessWidget { child: InkWell( splashColor: Colors.transparent, onTap: () { - viewModel.feedback(tenderId: tender.id!, feedbackType: 'like'); + viewModel.toogleFeedback(tenderId: tender.id!, feedbackType: 'like'); }, child: Column( children: [ From 0b2825f8368720e1036658c4ea66a1d5f77d5ed0 Mon Sep 17 00:00:00 2001 From: amirrezaghabeli Date: Wed, 27 Aug 2025 13:30:09 +0330 Subject: [PATCH 3/7] profile refactor --- lib/core/constants/strings.dart | 21 ++++++---------- .../detail/widgets/tender_detail_card.dart | 2 +- .../widgets/tender_card_progress_bar.dart | 2 +- lib/views/profile/pages/d_profile_page.dart | 18 ++++++------- lib/views/profile/pages/m_profile_page.dart | 15 ++++++----- lib/views/profile/pages/t_profile_page.dart | 25 +++++++++++-------- 6 files changed, 43 insertions(+), 40 deletions(-) diff --git a/lib/core/constants/strings.dart b/lib/core/constants/strings.dart index 21a8fa0..3a2b088 100644 --- a/lib/core/constants/strings.dart +++ b/lib/core/constants/strings.dart @@ -50,6 +50,12 @@ class AppStrings { static const String profile = 'Profile'; static const String settings = 'Settings'; static const String appTheme = 'App Theme'; + static const String companyName = 'Company Name'; + static const String nationalId = 'National ID'; + static const String registrationNumber = 'Registration No.'; + static const String businessType = 'Business Type'; + static const String founded = 'Founded'; + static const String logout = 'Logout'; //your tenders static const String yourTendersTitle = 'Your tenders'; @@ -73,21 +79,10 @@ class AppStrings { 'No experience in e-platform development'; static const String tenderSubmitButton = 'Submit'; static const String tenderRejectButton = 'Reject'; - static const String tenderDateExample = '2025-05-21'; - static const String tenderTitleExample = - 'Operation, support and further development of open e-platform Operation, support and further development of open e-platform'; - static const String tenderIdExample = 'JNDFKMDV-100-JF'; - static const String tenderApprovalDateExample = '2025-05-21'; - static const String tenderSubmissionDateExample = '2025-05-21'; + static const String tenderClientExample = 'Procurement Notice Procurement Notice'; - static const String tenderDeliveryLocationExample = 'Norrbotten County'; - static const String tenderReferenceNumberExample = 'KLF 2025/120'; - static const String tenderDescriptionExample = - 'Luleå Municipality is procuring on behalf of Norrbotten’s e-board (all municipalities in Norrbotten)...'; - static const String tenderMatchPercentageExample = '75%'; - static const String locationDescription = - 'Luleå Municipality is procuring on behalf of Norrbotten’s e-board (all municipalities in Norrbotten).

The procurement includes operation, support, and further development of the existing open e-platform.

No additional information, amendments, or answers to questions will be provided after June 16.o additional information, amendments, or answers to questions will be provided after June 16.o additional information, amendments, or answers to questions will be provided after June 16.website: www.tenders.com Lorem ipsum is amet Luleå Municipality is procuring on behalf of Norrbotten’s e-board (all municipalities in Norrbotten).

The procurement includes operation, support, and further development of the existing open e-platform.

No additional information, amendments, or answers to questions will be provided after June 16. o additional information, amendments, or answers to questions will be provided after June 16. o additional information, amendments, or answers to questions will be provided after June 16.website: www.tenders.com'; + static const String estimatedValue = 'Estimated Value '; static const String duration = 'Duration'; } diff --git a/lib/views/detail/widgets/tender_detail_card.dart b/lib/views/detail/widgets/tender_detail_card.dart index e6fe807..e357d40 100644 --- a/lib/views/detail/widgets/tender_detail_card.dart +++ b/lib/views/detail/widgets/tender_detail_card.dart @@ -33,7 +33,7 @@ class TenderDetailCard extends StatelessWidget { ), ), Text( - AppStrings.tenderMatchPercentageExample, + '75%', style: TextStyle( fontSize: 12.0.sp(), fontWeight: FontWeight.w600, diff --git a/lib/views/home/widgets/tender_card_progress_bar.dart b/lib/views/home/widgets/tender_card_progress_bar.dart index a7460bb..fd4aba5 100644 --- a/lib/views/home/widgets/tender_card_progress_bar.dart +++ b/lib/views/home/widgets/tender_card_progress_bar.dart @@ -23,7 +23,7 @@ class TenderCardProgressBar extends StatelessWidget { ), ), Text( - AppStrings.tenderMatchPercentageExample, + '75%', style: TextStyle( fontSize: 12.0.sp(), fontWeight: FontWeight.w600, diff --git a/lib/views/profile/pages/d_profile_page.dart b/lib/views/profile/pages/d_profile_page.dart index 85192c2..99414dd 100644 --- a/lib/views/profile/pages/d_profile_page.dart +++ b/lib/views/profile/pages/d_profile_page.dart @@ -81,34 +81,34 @@ class _DesktopProfilePageState extends State { break; case 2: // Already on profile - break; } }, ), SizedBox( - // color: Colors.amber.shade100, width: 740, - // height: 662, child: Column( children: [ SizedBox(height: 36.0.h()), TitleDescription( - title: 'Company Name', + title: AppStrings.companyName, description: viewModel.companyProfileData!.name!, ), TitleDescription( - title: 'National ID', + title: AppStrings.nationalId, description: viewModel.companyProfileData!.id!, ), TitleDescription( - title: 'Registration No.', + title: AppStrings.registrationNumber, description: viewModel.companyProfileData!.registrationNumber!, ), - TitleDescription(title: 'Business Type', description: '-'), TitleDescription( - title: 'Founded', + title: AppStrings.businessType, + description: '-', + ), + TitleDescription( + title: AppStrings.founded, description: viewModel.companyProfileData!.foundedYear!.toString(), ), @@ -168,7 +168,7 @@ class _DesktopProfilePageState extends State { ), alignment: Alignment.center, child: Text( - 'Logout', + AppStrings.logout, style: TextStyle( fontSize: 14.0.sp(), fontWeight: FontWeight.w500, diff --git a/lib/views/profile/pages/m_profile_page.dart b/lib/views/profile/pages/m_profile_page.dart index f0c2479..5cae8c6 100644 --- a/lib/views/profile/pages/m_profile_page.dart +++ b/lib/views/profile/pages/m_profile_page.dart @@ -74,21 +74,24 @@ class _MobileProfilePageState extends State { children: [ SizedBox(height: 36.0.h()), TitleDescription( - title: 'Company Name', + title: AppStrings.companyName, description: viewModel.companyProfileData!.name!, ), TitleDescription( - title: 'National ID', + title: AppStrings.nationalId, description: viewModel.companyProfileData!.id!, ), TitleDescription( - title: 'Registration No.', + title: AppStrings.registrationNumber, description: viewModel.companyProfileData!.registrationNumber!, ), - TitleDescription(title: 'Business Type', description: '-'), TitleDescription( - title: 'Founded', + title: AppStrings.businessType, + description: '-', + ), + TitleDescription( + title: AppStrings.founded, description: viewModel.companyProfileData!.foundedYear!.toString(), ), @@ -145,7 +148,7 @@ class _MobileProfilePageState extends State { ), alignment: Alignment.center, child: Text( - 'Logout', + AppStrings.logout, style: TextStyle( fontSize: 14.0.sp(), fontWeight: FontWeight.w500, diff --git a/lib/views/profile/pages/t_profile_page.dart b/lib/views/profile/pages/t_profile_page.dart index 1759a41..b6c66aa 100644 --- a/lib/views/profile/pages/t_profile_page.dart +++ b/lib/views/profile/pages/t_profile_page.dart @@ -118,22 +118,27 @@ class _TabletProfilePageState extends State { children: [ SizedBox(height: 128.0.h()), TitleDescription( - title: 'Company Name', - description: 'Telecom Solutions GmbH', + title: AppStrings.companyName, + description: viewModel.companyProfileData!.name!, ), TitleDescription( - title: 'National ID', - description: '12345678901', + title: AppStrings.nationalId, + description: viewModel.companyProfileData!.id!, ), TitleDescription( - title: 'Registration No.', - description: 'DE-55667788', + title: AppStrings.registrationNumber, + description: + viewModel.companyProfileData!.registrationNumber!, ), TitleDescription( - title: 'Business Type', - description: 'Telecommunications', + title: AppStrings.businessType, + description: '-', + ), + TitleDescription( + title: AppStrings.founded, + description: + viewModel.companyProfileData!.foundedYear!.toString(), ), - TitleDescription(title: 'Founded', description: '2010'), SizedBox(height: 24.0.h()), // Theme Toggle Align( @@ -190,7 +195,7 @@ class _TabletProfilePageState extends State { ), alignment: Alignment.center, child: Text( - 'Logout', + AppStrings.logout, style: TextStyle( fontSize: 14.0.sp(), fontWeight: FontWeight.w500, From c401c6c8d97e18df6d565d90c5e86ce9e6107f68 Mon Sep 17 00:00:00 2001 From: amirrezaghabeli Date: Wed, 27 Aug 2025 13:33:47 +0330 Subject: [PATCH 4/7] handle null error in profile --- lib/core/constants/strings.dart | 3 +++ lib/views/profile/pages/m_profile_page.dart | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/lib/core/constants/strings.dart b/lib/core/constants/strings.dart index 3a2b088..f1013a7 100644 --- a/lib/core/constants/strings.dart +++ b/lib/core/constants/strings.dart @@ -1,5 +1,8 @@ class AppStrings { AppStrings._(); + + static const String noData = 'No data'; + // App Titles static const String appName = 'My Awesome App'; static const String loginTitle = 'Welcome Back'; diff --git a/lib/views/profile/pages/m_profile_page.dart b/lib/views/profile/pages/m_profile_page.dart index 5cae8c6..9da448c 100644 --- a/lib/views/profile/pages/m_profile_page.dart +++ b/lib/views/profile/pages/m_profile_page.dart @@ -65,6 +65,10 @@ class _MobileProfilePageState extends State { return Center(child: Text(viewModel.errorMessage!)); } + if (viewModel.companyProfileData == null) { + return Center(child: Text(AppStrings.noData)); + } + return Padding( padding: EdgeInsets.symmetric( horizontal: 24.0.w(), From 8794627e7f0c8717c4b8cd3b98766257a8ee3771 Mon Sep 17 00:00:00 2001 From: amirrezaghabeli Date: Wed, 27 Aug 2025 13:34:49 +0330 Subject: [PATCH 5/7] profile tablet and web null check --- lib/views/profile/pages/d_profile_page.dart | 4 ++++ lib/views/profile/pages/t_profile_page.dart | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/lib/views/profile/pages/d_profile_page.dart b/lib/views/profile/pages/d_profile_page.dart index 99414dd..a43eff8 100644 --- a/lib/views/profile/pages/d_profile_page.dart +++ b/lib/views/profile/pages/d_profile_page.dart @@ -64,6 +64,10 @@ class _DesktopProfilePageState extends State { return Center(child: Text(viewModel.errorMessage!)); } + if (viewModel.companyProfileData == null) { + return Center(child: Text(AppStrings.noData)); + } + return Column( children: [ DesktopNavigationWidget( diff --git a/lib/views/profile/pages/t_profile_page.dart b/lib/views/profile/pages/t_profile_page.dart index b6c66aa..6ef28e5 100644 --- a/lib/views/profile/pages/t_profile_page.dart +++ b/lib/views/profile/pages/t_profile_page.dart @@ -64,6 +64,10 @@ class _TabletProfilePageState extends State { return Center(child: Text(viewModel.errorMessage!)); } + if (viewModel.companyProfileData == null) { + return Center(child: Text(AppStrings.noData)); + } + return SafeArea( child: Scaffold( key: key, From e7950cd3fc39c33fe46be6333c9f9afd93a073fd Mon Sep 17 00:00:00 2001 From: amirrezaghabeli Date: Wed, 27 Aug 2025 13:59:47 +0330 Subject: [PATCH 6/7] detail refactor --- lib/core/constants/strings.dart | 12 +- lib/core/utils/date_utils.dart | 111 ++++++++++++++---- lib/view_models/tender_detail_view_model.dart | 1 + .../detail/pages/detail_desktop_page.dart | 35 +++--- .../detail/pages/detail_mobile_page.dart | 34 +++--- .../detail/pages/detail_tablet_page.dart | 36 +++--- lib/views/detail/widgets/deadline_item.dart | 41 +++---- .../detail/widgets/tender_detail_header.dart | 15 +-- .../widgets/tender_detail_info_section.dart | 23 +--- 9 files changed, 171 insertions(+), 137 deletions(-) diff --git a/lib/core/constants/strings.dart b/lib/core/constants/strings.dart index f1013a7..abbff66 100644 --- a/lib/core/constants/strings.dart +++ b/lib/core/constants/strings.dart @@ -74,7 +74,6 @@ class AppStrings { static const String tenderDeliveryLocationsLabel = 'Delivery Locations'; static const String tenderReferenceNumberLabel = 'Reference Number'; static const String tenderLocationLabel = 'Locations'; - static const String tenderLocationCountry = 'UK'; static const String tenderPdfDocument = 'PDF , Document'; static const String tenderMatchProfile = 'Match with your profile'; static const String tenderIncompleteResume = 'Incomplete Resume Information'; @@ -82,10 +81,17 @@ class AppStrings { 'No experience in e-platform development'; static const String tenderSubmitButton = 'Submit'; static const String tenderRejectButton = 'Reject'; - static const String tenderClientExample = 'Procurement Notice Procurement Notice'; - static const String estimatedValue = 'Estimated Value '; static const String duration = 'Duration'; + static const String tenderSubmittedSuccessfully = + 'tender submitted successfully!'; + static const String tenderRejectedSuccessfully = + 'tender rejected successfully!'; + static const String tenderUnrejectedSuccessfully = + 'tender unrejected successfully!'; + static const String tenderUnsubmittedSuccessfully = + 'tender unsubmitted successfully!'; + static const String tenderNoData = 'No tender details available'; } diff --git a/lib/core/utils/date_utils.dart b/lib/core/utils/date_utils.dart index aabdfa9..cc98580 100644 --- a/lib/core/utils/date_utils.dart +++ b/lib/core/utils/date_utils.dart @@ -1,15 +1,15 @@ import 'package:intl/intl.dart'; -class DateUtils { +/// Extension on int to provide date formatting utilities for Unix timestamps +extension UnixTimestampExtension on int? { /// Converts Unix timestamp (seconds since epoch) to a formatted date string - static String unixToDate(int? unixTimestamp, {String format = 'yyyy-MM-dd'}) { - if (unixTimestamp == null) { + String toDate({String format = 'yyyy-MM-dd'}) { + if (this == null) { return ''; } // Convert seconds to milliseconds if needed - int timestampInMs = - unixTimestamp > 1000000000000 ? unixTimestamp : unixTimestamp * 1000; + int timestampInMs = this! > 1000000000000 ? this! : this! * 1000; final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch( timestampInMs, @@ -18,13 +18,12 @@ class DateUtils { } /// Converts Unix timestamp to a readable date string - static String unixToReadableDate(int? unixTimestamp) { - if (unixTimestamp == null) { + String toReadableDate() { + if (this == null) { return ''; } - int timestampInMs = - unixTimestamp > 1000000000000 ? unixTimestamp : unixTimestamp * 1000; + int timestampInMs = this! > 1000000000000 ? this! : this! * 1000; final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch( timestampInMs, @@ -44,13 +43,12 @@ class DateUtils { } /// Converts Unix timestamp to a short date format - static String unixToShortDate(int? unixTimestamp) { - if (unixTimestamp == null) { + String toShortDate() { + if (this == null) { return ''; } - int timestampInMs = - unixTimestamp > 1000000000000 ? unixTimestamp : unixTimestamp * 1000; + int timestampInMs = this! > 1000000000000 ? this! : this! * 1000; final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch( timestampInMs, @@ -59,13 +57,12 @@ class DateUtils { } /// Converts Unix timestamp to a full date and time format - static String unixToDateTime(int? unixTimestamp) { - if (unixTimestamp == null) { + String toDateTime() { + if (this == null) { return ''; } - int timestampInMs = - unixTimestamp > 1000000000000 ? unixTimestamp : unixTimestamp * 1000; + int timestampInMs = this! > 1000000000000 ? this! : this! * 1000; final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch( timestampInMs, @@ -74,13 +71,12 @@ class DateUtils { } /// Checks if a Unix timestamp is in the past - static bool isPast(int? unixTimestamp) { - if (unixTimestamp == null) { + bool get isPast { + if (this == null) { return false; } - int timestampInMs = - unixTimestamp > 1000000000000 ? unixTimestamp : unixTimestamp * 1000; + int timestampInMs = this! > 1000000000000 ? this! : this! * 1000; final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch( timestampInMs, @@ -89,13 +85,12 @@ class DateUtils { } /// Gets the number of days remaining until a Unix timestamp - static int daysRemaining(int? unixTimestamp) { - if (unixTimestamp == null) { + int get daysRemaining { + if (this == null) { return 0; } - int timestampInMs = - unixTimestamp > 1000000000000 ? unixTimestamp : unixTimestamp * 1000; + int timestampInMs = this! > 1000000000000 ? this! : this! * 1000; final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch( timestampInMs, @@ -106,3 +101,69 @@ class DateUtils { return difference.inDays; } } + +/// Extension on DateTime to provide additional formatting utilities +extension DateTimeExtension on DateTime { + /// Formats the DateTime to a readable string + String toReadableString() { + final DateTime now = DateTime.now(); + final Duration difference = now.difference(this); + + if (difference.inDays == 0) { + return 'Today'; + } else if (difference.inDays == 1) { + return 'Yesterday'; + } else if (difference.inDays < 7) { + return '${difference.inDays} days ago'; + } else { + return DateFormat('MMM dd, yyyy').format(this); + } + } + + /// Formats the DateTime to a short date string + String toShortDate() { + return DateFormat('MMM dd').format(this); + } + + /// Formats the DateTime to a full date and time string + String toDateTimeString() { + return DateFormat('MMM dd, yyyy HH:mm').format(this); + } + + /// Checks if the DateTime is in the past + bool get isPast { + return isBefore(DateTime.now()); + } + + /// Gets the number of days remaining until this DateTime + int get daysRemaining { + final DateTime now = DateTime.now(); + final Duration difference = this.difference(now); + return difference.inDays; + } +} + +/// Utility class for date operations +class DateUtils { + /// Converts Unix timestamp to a formatted date string + + static String unixToDate(int? timestamp) { + if (timestamp == null) { + return ''; + } + + // Convert seconds to milliseconds if needed + int timestampInMs = + timestamp > 1000000000000 ? timestamp : timestamp * 1000; + + final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch( + timestampInMs, + ); + return DateFormat('MMM dd, yyyy').format(dateTime); + } +} + +/// Global function for backward compatibility +String unixToDate(int? timestamp) { + return DateUtils.unixToDate(timestamp); +} diff --git a/lib/view_models/tender_detail_view_model.dart b/lib/view_models/tender_detail_view_model.dart index d923401..511afb5 100644 --- a/lib/view_models/tender_detail_view_model.dart +++ b/lib/view_models/tender_detail_view_model.dart @@ -121,6 +121,7 @@ class TenderDetailViewModel with ChangeNotifier { final result = await _tendersRepository.getTenderApprovals( tenderId: tenderId, ); + approvalStatus = false; switch (result) { case Ok(): _tenderApprovalData = result.value.data; diff --git a/lib/views/detail/pages/detail_desktop_page.dart b/lib/views/detail/pages/detail_desktop_page.dart index 7401d44..876b527 100644 --- a/lib/views/detail/pages/detail_desktop_page.dart +++ b/lib/views/detail/pages/detail_desktop_page.dart @@ -8,6 +8,7 @@ import 'package:tm_app/views/detail/widgets/tender_detail_action.dart'; import 'package:tm_app/views/detail/widgets/tender_detail_card.dart'; import 'package:tm_app/views/detail/widgets/tender_detail_header.dart'; +import '../../../core/constants/strings.dart'; import '../../../view_models/home_view_model.dart'; import '../../../view_models/tenders_view_model.dart'; import '../../shared/desktop_navigation_widget.dart'; @@ -34,39 +35,39 @@ class _TenderDetailDesktopPageState extends State { if (viewModel.status.isNotEmpty && viewModel.status == 'submitted' && viewModel.approvalStatus == true) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('tender submitted successfully!'))); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(AppStrings.tenderSubmittedSuccessfully)), + ); } if (viewModel.status.isNotEmpty && viewModel.status == 'unsubmitted' && viewModel.approvalStatus == true) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('tender submitted removed sucessfully!')), + SnackBar(content: Text(AppStrings.tenderUnsubmittedSuccessfully)), ); } if (viewModel.status.isNotEmpty && viewModel.status == 'rejected' && viewModel.approvalStatus == true) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('tender rejected sucessfully!'))); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(AppStrings.tenderRejectedSuccessfully)), + ); } if (viewModel.status.isNotEmpty && viewModel.status == 'unrejected' && viewModel.approvalStatus == true) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('tender reject removed sucessfully!')), + SnackBar(content: Text(AppStrings.tenderUnrejectedSuccessfully)), + ); + } + if (viewModel.approvalStatus != false && viewModel.errorMessage != null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: AppColors.errorColor, + content: Text(viewModel.errorMessage!), + ), ); } - // if (viewModel.errorMessage != null) { - // ScaffoldMessenger.of(context).showSnackBar( - // SnackBar( - // backgroundColor: AppColors.errorColor, - // content: Text(viewModel.errorMessage!), - // ), - // ); - // } } @override @@ -99,7 +100,7 @@ class _TenderDetailDesktopPageState extends State { final detail = tenderViewModel.tenderDetail; if (detail == null) { - return const Center(child: Text('No tender details available')); + return const Center(child: Text(AppStrings.tenderNoData)); } return Column( diff --git a/lib/views/detail/pages/detail_mobile_page.dart b/lib/views/detail/pages/detail_mobile_page.dart index 09986ce..7d67fc3 100644 --- a/lib/views/detail/pages/detail_mobile_page.dart +++ b/lib/views/detail/pages/detail_mobile_page.dart @@ -30,39 +30,39 @@ class _TenderDetailMobilePageState extends State { if (viewModel.status.isNotEmpty && viewModel.status == 'submitted' && viewModel.approvalStatus == true) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('tender submitted successfully!'))); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(AppStrings.tenderSubmittedSuccessfully)), + ); } if (viewModel.status.isNotEmpty && viewModel.status == 'unsubmitted' && viewModel.approvalStatus == true) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('tender submitted removed sucessfully!')), + SnackBar(content: Text(AppStrings.tenderUnsubmittedSuccessfully)), ); } if (viewModel.status.isNotEmpty && viewModel.status == 'rejected' && viewModel.approvalStatus == true) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('tender rejected sucessfully!'))); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(AppStrings.tenderRejectedSuccessfully)), + ); } if (viewModel.status.isNotEmpty && viewModel.status == 'unrejected' && viewModel.approvalStatus == true) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('tender reject removed sucessfully!')), + SnackBar(content: Text(AppStrings.tenderUnrejectedSuccessfully)), + ); + } + if (viewModel.approvalStatus != false && viewModel.errorMessage != null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: AppColors.errorColor, + content: Text(viewModel.errorMessage!), + ), ); } - // if (viewModel.errorMessage != null) { - // ScaffoldMessenger.of(context).showSnackBar( - // SnackBar( - // backgroundColor: AppColors.errorColor, - // content: Text(viewModel.errorMessage!), - // ), - // ); - // } } @override @@ -90,7 +90,7 @@ class _TenderDetailMobilePageState extends State { final detail = tenderViewModel.tenderDetail; if (detail == null) { - return const Center(child: Text('No tender details available')); + return const Center(child: Text(AppStrings.tenderNoData)); } return SingleChildScrollView( diff --git a/lib/views/detail/pages/detail_tablet_page.dart b/lib/views/detail/pages/detail_tablet_page.dart index fc5ed57..dfac552 100644 --- a/lib/views/detail/pages/detail_tablet_page.dart +++ b/lib/views/detail/pages/detail_tablet_page.dart @@ -12,6 +12,8 @@ import 'package:tm_app/views/detail/widgets/tender_detail_card.dart'; import 'package:tm_app/views/detail/widgets/tender_detail_header.dart'; import 'package:tm_app/views/shared/tablet_navigation_widget.dart'; +import '../../../core/constants/strings.dart'; + class TenderDetailTabletPage extends StatefulWidget { final String tenderId; const TenderDetailTabletPage({required this.tenderId, super.key}); @@ -33,39 +35,39 @@ class _TenderDetailTabletPageState extends State { if (viewModel.status.isNotEmpty && viewModel.status == 'submitted' && viewModel.approvalStatus == true) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('tender submitted successfully!'))); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(AppStrings.tenderSubmittedSuccessfully)), + ); } if (viewModel.status.isNotEmpty && viewModel.status == 'unsubmitted' && viewModel.approvalStatus == true) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('tender submitted removed sucessfully!')), + SnackBar(content: Text(AppStrings.tenderUnsubmittedSuccessfully)), ); } if (viewModel.status.isNotEmpty && viewModel.status == 'rejected' && viewModel.approvalStatus == true) { - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text('tender rejected sucessfully!'))); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(AppStrings.tenderRejectedSuccessfully)), + ); } if (viewModel.status.isNotEmpty && viewModel.status == 'unrejected' && viewModel.approvalStatus == true) { ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('tender reject removed sucessfully!')), + SnackBar(content: Text(AppStrings.tenderUnrejectedSuccessfully)), + ); + } + if (viewModel.approvalStatus != false && viewModel.errorMessage != null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: AppColors.errorColor, + content: Text(viewModel.errorMessage!), + ), ); } - // if (viewModel.errorMessage != null) { - // ScaffoldMessenger.of(context).showSnackBar( - // SnackBar( - // backgroundColor: AppColors.errorColor, - // content: Text(viewModel.errorMessage!), - // ), - // ); - // } } @override @@ -138,7 +140,7 @@ class _TenderDetailTabletPageState extends State { final detail = tenderViewModel.tenderDetail; if (detail == null) { - return const Center(child: Text('No tender details available')); + return const Center(child: Text(AppStrings.tenderNoData)); } return SingleChildScrollView( child: Center( diff --git a/lib/views/detail/widgets/deadline_item.dart b/lib/views/detail/widgets/deadline_item.dart index e77bbdb..e894948 100644 --- a/lib/views/detail/widgets/deadline_item.dart +++ b/lib/views/detail/widgets/deadline_item.dart @@ -1,21 +1,18 @@ import 'package:flutter/material.dart'; import 'package:tm_app/core/theme/colors.dart'; +import 'package:tm_app/core/utils/date_utils.dart'; import 'package:tm_app/core/utils/size_config.dart'; +import '../../../core/constants/strings.dart'; +import '../../../data/services/model/tender_data/tender_data.dart'; + class DeadlineItem extends StatelessWidget { - final String title; - final String approvalText; - final String approvalDate; - final String submissionText; - final String submissionDate; + final TenderData detail; final bool isScreenBig; const DeadlineItem({ - required this.title, - required this.approvalText, - required this.approvalDate, - required this.submissionText, - required this.submissionDate, + required this.detail, + required this.isScreenBig, super.key, }); @@ -31,7 +28,7 @@ class DeadlineItem extends StatelessWidget { SizedBox( height: 45.0.h(), child: Text( - title, + detail.title ?? '', style: TextStyle( color: AppColors.grey80, fontWeight: FontWeight.w600, @@ -47,7 +44,7 @@ class DeadlineItem extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - approvalText, + AppStrings.tenderApprovalText, style: TextStyle( color: AppColors.grey70, fontWeight: FontWeight.w400, @@ -56,7 +53,7 @@ class DeadlineItem extends StatelessWidget { ), SizedBox(height: 4.0.h()), Text( - approvalDate, + unixToDate(detail.submissionDeadline), style: TextStyle( color: AppColors.grey80, fontWeight: FontWeight.w400, @@ -76,7 +73,7 @@ class DeadlineItem extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - submissionText, + AppStrings.tenderSubmissionText, style: TextStyle( color: AppColors.grey70, fontWeight: FontWeight.w400, @@ -85,7 +82,7 @@ class DeadlineItem extends StatelessWidget { ), SizedBox(height: 4.0.h()), Text( - submissionDate, + unixToDate(detail.applicationDeadline), style: TextStyle( color: AppColors.grey80, fontWeight: FontWeight.w400, @@ -99,7 +96,7 @@ class DeadlineItem extends StatelessWidget { ), ), SizedBox(height: 8.0.h()), - Divider(color: AppColors.grey20,), + Divider(color: AppColors.grey20), ], ), ) @@ -111,7 +108,7 @@ class DeadlineItem extends StatelessWidget { SizedBox( height: 45.0.h(), child: Text( - title, + detail.title ?? '', style: TextStyle( color: AppColors.grey80, fontWeight: FontWeight.w600, @@ -123,7 +120,7 @@ class DeadlineItem extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - approvalText, + AppStrings.tenderApprovalText, style: TextStyle( color: AppColors.grey70, fontWeight: FontWeight.w400, @@ -131,7 +128,7 @@ class DeadlineItem extends StatelessWidget { ), ), Text( - approvalDate, + unixToDate(detail.submissionDeadline), style: TextStyle( color: AppColors.grey70, fontWeight: FontWeight.w400, @@ -145,7 +142,7 @@ class DeadlineItem extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - submissionText, + AppStrings.tenderSubmissionText, style: TextStyle( color: AppColors.grey70, fontWeight: FontWeight.w400, @@ -153,7 +150,7 @@ class DeadlineItem extends StatelessWidget { ), ), Text( - submissionDate, + unixToDate(detail.applicationDeadline), style: TextStyle( color: AppColors.grey70, fontWeight: FontWeight.w400, @@ -163,7 +160,7 @@ class DeadlineItem extends StatelessWidget { ], ), SizedBox(height: 8.0.h()), - Divider(color: AppColors.grey20,), + Divider(color: AppColors.grey20), ], ), ); diff --git a/lib/views/detail/widgets/tender_detail_header.dart b/lib/views/detail/widgets/tender_detail_header.dart index b38abab..4a9b24c 100644 --- a/lib/views/detail/widgets/tender_detail_header.dart +++ b/lib/views/detail/widgets/tender_detail_header.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; import 'package:tm_app/core/theme/colors.dart'; +import 'package:tm_app/core/utils/date_utils.dart'; import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/data/services/model/tender_data/tender_data.dart'; import 'package:tm_app/views/detail/widgets/status_tag.dart'; @@ -73,16 +73,3 @@ class TenderDetailHeader extends StatelessWidget { ), ); } - -String unixToDate(int? unixTimestamp, {String format = 'yyyy-MM-dd'}) { - if (unixTimestamp == null) { - return ''; - } - - // Convert seconds to milliseconds if needed - int timestampInMs = - unixTimestamp > 1000000000000 ? unixTimestamp : unixTimestamp * 1000; - - final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(timestampInMs); - return DateFormat(format).format(dateTime); -} diff --git a/lib/views/detail/widgets/tender_detail_info_section.dart b/lib/views/detail/widgets/tender_detail_info_section.dart index 7dee5e4..0486e93 100644 --- a/lib/views/detail/widgets/tender_detail_info_section.dart +++ b/lib/views/detail/widgets/tender_detail_info_section.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:intl/intl.dart'; import 'package:tm_app/core/constants/strings.dart'; import 'package:tm_app/data/services/model/tender_data/tender_data.dart'; import 'package:tm_app/views/detail/widgets/deadline_item.dart'; @@ -21,14 +20,7 @@ class TenderDetailInfoSection extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ InfoItem(title: AppStrings.tenderIdLabel, value: detail.tenderId ?? ''), - DeadlineItem( - title: AppStrings.tenderDeadlineLabel, - approvalText: AppStrings.tenderApprovalText, - approvalDate: unixToDate(detail.submissionDeadline), - submissionText: AppStrings.tenderSubmissionText, - submissionDate: unixToDate(detail.applicationDeadline), - isScreenBig: isScreenBig, - ), + DeadlineItem(detail: detail, isScreenBig: isScreenBig), InfoItem( title: AppStrings.tenderClientLabel, value: detail.buyerOrganization!.name ?? '', @@ -49,16 +41,3 @@ class TenderDetailInfoSection extends StatelessWidget { ); } } - -String unixToDate(int? unixTimestamp, {String format = 'yyyy-MM-dd'}) { - if (unixTimestamp == null) { - return ''; - } - - // Convert seconds to milliseconds if needed - int timestampInMs = - unixTimestamp > 1000000000000 ? unixTimestamp : unixTimestamp * 1000; - - final DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(timestampInMs); - return DateFormat(format).format(dateTime); -} From f6ab5e0560ee07ae80a735b2eb631b2701b247b2 Mon Sep 17 00:00:00 2001 From: amirrezaghabeli Date: Wed, 27 Aug 2025 14:06:57 +0330 Subject: [PATCH 7/7] tenders refactor --- lib/views/tenders/models/tender_model.dart | 94 --------------------- lib/views/tenders/pages/d_tenders_page.dart | 6 ++ lib/views/tenders/pages/m_tenders_page.dart | 5 ++ lib/views/tenders/pages/t_tenders_page.dart | 5 ++ lib/views/tenders/widgets/tender_card.dart | 53 ------------ 5 files changed, 16 insertions(+), 147 deletions(-) delete mode 100644 lib/views/tenders/models/tender_model.dart diff --git a/lib/views/tenders/models/tender_model.dart b/lib/views/tenders/models/tender_model.dart deleted file mode 100644 index f270bd0..0000000 --- a/lib/views/tenders/models/tender_model.dart +++ /dev/null @@ -1,94 +0,0 @@ -class Tender { - final String date; - final String deadline; - final String title; - final String description; - final String tenderId; - final String location; - final String country; - final double matchPercentage; - - Tender({ - required this.date, - required this.deadline, - required this.title, - required this.description, - required this.tenderId, - required this.location, - required this.country, - required this.matchPercentage, - }); - - factory Tender.fromJson(Map json) { - return Tender( - date: json['date'] as String, - deadline: json['deadline'] as String, - title: json['title'] as String, - description: json['description'] as String, - tenderId: json['tenderId'] as String, - location: json['location'] as String, - country: json['country'] as String, - matchPercentage: (json['matchPercentage'] as num).toDouble(), - ); - } - - Map toJson() { - return { - 'date': date, - 'deadline': deadline, - 'title': title, - 'description': description, - 'tenderId': tenderId, - 'location': location, - 'country': country, - 'matchPercentage': matchPercentage, - }; - } -} - -final List sampleTenders = [ - Tender( - date: '2025-05-20', - deadline: '2025-06-10', - title: 'Digital Services Tender', - description: - 'A tender for digital services and IT infrastructure development. The project involves modernizing existing systems and implementing new digital solutions for improved efficiency.', - tenderId: 'KLF 2025/119', - location: 'Stockholm', - country: 'SE', - matchPercentage: 85.0, - ), - Tender( - date: '2025-04-15', - deadline: '2025-05-01', - title: 'Construction Project', - description: - 'Seeking contractors for a large-scale construction project in the city center. Includes both residential and commercial buildings.', - tenderId: 'CST 2025/042', - location: 'Gothenburg', - country: 'SE', - matchPercentage: 72.0, - ), - Tender( - date: '2025-03-10', - deadline: '2025-04-05', - title: 'Healthcare Equipment Supply', - description: - 'Supplying medical and healthcare equipment to regional hospitals. Includes installation and maintenance services.', - tenderId: 'HLT 2025/301', - location: 'Malmö', - country: 'SE', - matchPercentage: 90.0, - ), - Tender( - date: '2025-02-28', - deadline: '2025-03-20', - title: 'IT Consulting Services', - description: - 'Looking for experienced IT consultants to support digital transformation initiatives for public sector organizations.', - tenderId: 'ITC 2025/210', - location: 'Uppsala', - country: 'SE', - matchPercentage: 78.0, - ), -]; diff --git a/lib/views/tenders/pages/d_tenders_page.dart b/lib/views/tenders/pages/d_tenders_page.dart index d6d17a4..25b071e 100644 --- a/lib/views/tenders/pages/d_tenders_page.dart +++ b/lib/views/tenders/pages/d_tenders_page.dart @@ -8,6 +8,8 @@ import 'package:tm_app/view_models/tenders_view_model.dart'; import 'package:tm_app/views/shared/desktop_navigation_widget.dart'; import 'package:tm_app/views/tenders/widgets/main_tenders_slider.dart'; +import '../../../core/constants/strings.dart'; + class DesktopTendersPage extends StatefulWidget { const DesktopTendersPage({super.key}); @@ -91,6 +93,10 @@ class _DesktopTendersPageState extends State { if (viewModel.errorMessage != null) { return Center(child: Text(viewModel.errorMessage!)); } + if (viewModel.tendersResponse?.data?.tenders == null || + viewModel.tendersResponse!.data!.tenders!.isEmpty) { + return Center(child: Text(AppStrings.noData)); + } return SizedBox( width: 740, height: 800, diff --git a/lib/views/tenders/pages/m_tenders_page.dart b/lib/views/tenders/pages/m_tenders_page.dart index ec761c9..4691cf6 100644 --- a/lib/views/tenders/pages/m_tenders_page.dart +++ b/lib/views/tenders/pages/m_tenders_page.dart @@ -65,6 +65,11 @@ class _MobileTendersPageState extends State { return Center(child: Text(viewModel.errorMessage!)); } + if (viewModel.tendersResponse?.data?.tenders == null || + viewModel.tendersResponse!.data!.tenders!.isEmpty) { + return Center(child: Text(AppStrings.noData)); + } + return Padding( padding: EdgeInsets.symmetric(vertical: 24.0.h()), child: MainTendersSlider( diff --git a/lib/views/tenders/pages/t_tenders_page.dart b/lib/views/tenders/pages/t_tenders_page.dart index fe9c734..bcf2735 100644 --- a/lib/views/tenders/pages/t_tenders_page.dart +++ b/lib/views/tenders/pages/t_tenders_page.dart @@ -10,6 +10,7 @@ import 'package:tm_app/views/shared/tablet_navigation_widget.dart'; import 'package:tm_app/views/tenders/widgets/main_tenders_slider.dart'; import '../../../core/constants/assets.dart'; +import '../../../core/constants/strings.dart'; class TabletTendersPage extends StatelessWidget { const TabletTendersPage({super.key}); @@ -73,6 +74,10 @@ class TabletTendersPage extends StatelessWidget { if (viewModel.errorMessage != null) { return Center(child: Text(viewModel.errorMessage!)); } + if (viewModel.tendersResponse?.data?.tenders == null || + viewModel.tendersResponse!.data!.tenders!.isEmpty) { + return Center(child: Text(AppStrings.noData)); + } return Padding( padding: EdgeInsets.only(right: 24.0.w(), top: 128.0.h()), child: MainTendersSlider( diff --git a/lib/views/tenders/widgets/tender_card.dart b/lib/views/tenders/widgets/tender_card.dart index 5969df2..66cbdc7 100644 --- a/lib/views/tenders/widgets/tender_card.dart +++ b/lib/views/tenders/widgets/tender_card.dart @@ -154,59 +154,6 @@ class TenderCard extends StatelessWidget { ); } - // Widget _approvalStatusBadge() { - // return Consumer( - // builder: (context, viewModel, child) { - // final approvalStatus = viewModel.getApprovalStatusForTender(tender.id!); - // final statusText = approvalStatus ?? 'Pending'; - - // Color statusColor; - // switch (approvalStatus) { - // case 'approved': - // statusColor = AppColors.successColor; - // break; - // case 'rejected': - // statusColor = AppColors.errorColor; - // break; - // default: - // statusColor = AppColors.grey60; - // } - - // return Container( - // width: double.infinity, - // height: 24.0.h(), - // padding: EdgeInsets.symmetric(horizontal: 8.0.w()), - // decoration: BoxDecoration( - // color: AppColors.grey20, - // borderRadius: BorderRadius.circular(4), - // border: Border.all(color: statusColor), - // ), - // child: Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // Text( - // 'Approval Status', - // style: TextStyle( - // color: AppColors.grey80, - // fontSize: 14.0.sp(), - // fontWeight: FontWeight.w500, - // ), - // ), - // Text( - // statusText.toUpperCase(), - // style: TextStyle( - // color: statusColor, - // fontSize: 14.0.sp(), - // fontWeight: FontWeight.w500, - // ), - // ), - // ], - // ), - // ); - // }, - // ); - // } - Widget _tenderTitle() { return Text( tender.title ?? '',