From e49c12b6cc8cce8721f0398f96c4f33b69132383 Mon Sep 17 00:00:00 2001 From: amirrezaghabeli Date: Thu, 25 Sep 2025 12:48:32 +0330 Subject: [PATCH] merge with conflict --- .../routes/app_shell/mobile_shell_page.dart | 25 +++++- lib/data/repositories/home_repository.dart | 4 + lib/data/services/api/home_api.dart | 6 +- lib/data/services/home_service.dart | 9 +++ lib/view_models/home_view_model.dart | 18 +++++ lib/view_models/notification_view_model.dart | 4 +- .../shared/desktop_navigation_widget.dart | 81 ++++++++++++------- .../shared/tablet_navigation_widget.dart | 22 ++++- 8 files changed, 130 insertions(+), 39 deletions(-) diff --git a/lib/core/routes/app_shell/mobile_shell_page.dart b/lib/core/routes/app_shell/mobile_shell_page.dart index 5c995c9..f6e74ab 100644 --- a/lib/core/routes/app_shell/mobile_shell_page.dart +++ b/lib/core/routes/app_shell/mobile_shell_page.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:go_router/go_router.dart'; +import 'package:provider/provider.dart'; import 'package:tm_app/core/constants/assets.dart'; import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/core/utils/size_config.dart'; +import 'package:tm_app/view_models/home_view_model.dart'; import 'package:tm_app/views/profile/strings/profile_strings.dart'; import '../../../views/home/strings/home_strings.dart'; @@ -63,6 +65,7 @@ class MobileShellPage extends StatelessWidget { onTap: () => onTap(3), iconPath: AssetsManager.notify, activeIconPath: AssetsManager.notifyActive, + showBadge: context.watch().hasUnreadNotification, ), _bottomNavigationItem( context: context, @@ -85,6 +88,7 @@ class MobileShellPage extends StatelessWidget { required VoidCallback onTap, required String iconPath, required String activeIconPath, + bool showBadge = false, }) { return InkWell( onTap: onTap, @@ -96,8 +100,25 @@ class MobileShellPage extends StatelessWidget { child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - SvgPicture.asset(isActive ? activeIconPath : iconPath), - + Stack( + clipBehavior: Clip.none, + children: [ + SvgPicture.asset(isActive ? activeIconPath : iconPath), + if (showBadge) + Positioned( + right: -5, + top: -2, + child: Container( + width: 8, + height: 8, + decoration: const BoxDecoration( + color: Colors.red, + shape: BoxShape.circle, + ), + ), + ), + ], + ), Text( text, maxLines: 1, diff --git a/lib/data/repositories/home_repository.dart b/lib/data/repositories/home_repository.dart index ea7df6c..1855bb1 100644 --- a/lib/data/repositories/home_repository.dart +++ b/lib/data/repositories/home_repository.dart @@ -37,4 +37,8 @@ class HomeRepository { Future> getFeedbackStats() { return _homeService.getFeedbackStats(); } + Future>> checkUnreadNotifications() async { + return _homeService.checkUnreadNotifications(); +} + } diff --git a/lib/data/services/api/home_api.dart b/lib/data/services/api/home_api.dart index c0d4cc2..558ea1d 100644 --- a/lib/data/services/api/home_api.dart +++ b/lib/data/services/api/home_api.dart @@ -9,13 +9,11 @@ class HomeApi { static const String tenderApprovalsBase = '/api/v1/tender-approvals'; - static String getYourTenders({ - required int limit, - required int offset, - }) { + static String getYourTenders({required int limit, required int offset}) { return '$tenderApprovalsBase?limit=$limit&offset=$offset'; } static const String tenderApprovalStats = '/api/v1/tender-approvals/stats'; static const String statsCompany = '/api/v1/feedback/stats/company'; + static const String checkUnreadNotifications = '/api/v1/notifications?seen=false&limit=1'; } diff --git a/lib/data/services/home_service.dart b/lib/data/services/home_service.dart index d81e1d4..b7131e8 100644 --- a/lib/data/services/home_service.dart +++ b/lib/data/services/home_service.dart @@ -41,4 +41,13 @@ class HomeService { ); return result; } + + Future>> checkUnreadNotifications() async { + final result = await _networkManager.makeRequest( + HomeApi.checkUnreadNotifications, + (json) => json, + method: 'GET', + ); + return result; + } } diff --git a/lib/view_models/home_view_model.dart b/lib/view_models/home_view_model.dart index 1490bb4..e2708f9 100644 --- a/lib/view_models/home_view_model.dart +++ b/lib/view_models/home_view_model.dart @@ -44,6 +44,9 @@ class HomeViewModel with ChangeNotifier { bool get hasMore => _hasMore; bool get isRecommendedMode => _isRecommendedMode; + bool _hasUnreadNotification = false; + bool get hasUnreadNotification => _hasUnreadNotification; + void init() async { _isLoading = true; notifyListeners(); @@ -51,6 +54,7 @@ class HomeViewModel with ChangeNotifier { await getApprovedStates(); await getYourTenders(reset: true); await getFeedbackStats(); + await checkUnreadNotifications(); if (_errorMessage != null) { _isLoading = false; @@ -206,4 +210,18 @@ class HomeViewModel with ChangeNotifier { double get selfApplyPercent { return totalCount > 0 ? (selfApplyCount / totalCount) * 100 : 0; } + + Future checkUnreadNotifications() async { + final result = await _homeRepository.checkUnreadNotifications(); + switch (result) { + case Ok>(): + final response = result.value; + _hasUnreadNotification = response['success'] == true; + break; + case Error>(): + _hasUnreadNotification = false; + break; + } + notifyListeners(); + } } diff --git a/lib/view_models/notification_view_model.dart b/lib/view_models/notification_view_model.dart index 72b1c88..82150c9 100644 --- a/lib/view_models/notification_view_model.dart +++ b/lib/view_models/notification_view_model.dart @@ -45,9 +45,9 @@ class NotificationViewModel with ChangeNotifier { /// Fetch notifications Future getNotifications({int page = 1, bool isMobile = false}) async { if (isMobile && page > 1) { - _isMoreLoading = true; // 👈 لودینگ فقط برای پیج بعدی + _isMoreLoading = true; } else { - _isLoading = true; // 👈 لودینگ عادی (اولین بار یا رفرش) + _isLoading = true; } notifyListeners(); diff --git a/lib/views/shared/desktop_navigation_widget.dart b/lib/views/shared/desktop_navigation_widget.dart index 7447f7e..9ae485e 100644 --- a/lib/views/shared/desktop_navigation_widget.dart +++ b/lib/views/shared/desktop_navigation_widget.dart @@ -148,36 +148,57 @@ class DesktopNavigationWidget extends StatelessWidget { ); } - Widget _navigationItem({ - required BuildContext context, - required String text, - required bool isActive, - required VoidCallback onTap, - required String iconPath, - required String activeIconPath, - }) { - return InkWell( - onTap: onTap, - child: SizedBox( - width: 115.0, - height: 64, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SvgPicture.asset(isActive ? activeIconPath : iconPath), - const SizedBox(width: 8), - Text( - text, - style: TextStyle( - fontSize: 12.0.sp(), - fontWeight: FontWeight.w400, - color: isActive ? AppColors.primaryColor : AppColors.grey60, - ), +Widget _navigationItem({ + required BuildContext context, + required String text, + required bool isActive, + required VoidCallback onTap, + required String iconPath, + required String activeIconPath, +}) { + final hasUnread = context.watch().hasUnreadNotification; + + return InkWell( + onTap: onTap, + child: SizedBox( + width: 115.0, + height: 64, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Stack( + clipBehavior: Clip.none, + children: [ + SvgPicture.asset(isActive ? activeIconPath : iconPath), + if (text == TendersStrings.notifications && hasUnread) + Positioned( + right: -5, + top: -2, + child: Container( + width: 8, + height: 8, + decoration: const BoxDecoration( + color: Colors.red, + shape: BoxShape.circle, + ), + ), + ), + ], + ), + const SizedBox(width: 8), + Text( + text, + style: TextStyle( + fontSize: 12.0.sp(), + fontWeight: FontWeight.w400, + color: isActive ? AppColors.primaryColor : AppColors.grey60, ), - ], - ), + ), + ], ), - ); - } + ), + ); +} + } diff --git a/lib/views/shared/tablet_navigation_widget.dart b/lib/views/shared/tablet_navigation_widget.dart index 0082cfb..d2043ca 100644 --- a/lib/views/shared/tablet_navigation_widget.dart +++ b/lib/views/shared/tablet_navigation_widget.dart @@ -129,6 +129,8 @@ class TabletNavigationWidget extends StatelessWidget { required String iconPath, required String activeIconPath, }) { + final hasUnread = context.watch().hasUnreadNotification; + return InkWell( onTap: onTap, child: Container( @@ -139,7 +141,25 @@ class TabletNavigationWidget extends StatelessWidget { padding: EdgeInsetsDirectional.only(start: 24.0.w()), child: Row( children: [ - SvgPicture.asset(isActive ? activeIconPath : iconPath), + Stack( + clipBehavior: Clip.none, + children: [ + SvgPicture.asset(isActive ? activeIconPath : iconPath), + if (text == ProfileStrings.notifications && hasUnread) + Positioned( + right: -5, + top: -2, + child: Container( + width: 8, + height: 8, + decoration: const BoxDecoration( + color: Colors.red, + shape: BoxShape.circle, + ), + ), + ), + ], + ), SizedBox(width: 12.0.w()), Text( text,