merge with conflict

This commit is contained in:
amirrezaghabeli
2025-09-25 12:48:32 +03:30
parent 9d22bccef0
commit e49c12b6cc
8 changed files with 130 additions and 39 deletions
@@ -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<HomeViewModel>().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,
@@ -37,4 +37,8 @@ class HomeRepository {
Future<Result<FeedbackStatResponse>> getFeedbackStats() {
return _homeService.getFeedbackStats();
}
Future<Result<Map<String, dynamic>>> checkUnreadNotifications() async {
return _homeService.checkUnreadNotifications();
}
}
+2 -4
View File
@@ -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';
}
+9
View File
@@ -41,4 +41,13 @@ class HomeService {
);
return result;
}
Future<Result<Map<String, dynamic>>> checkUnreadNotifications() async {
final result = await _networkManager.makeRequest(
HomeApi.checkUnreadNotifications,
(json) => json,
method: 'GET',
);
return result;
}
}
+18
View File
@@ -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<void> checkUnreadNotifications() async {
final result = await _homeRepository.checkUnreadNotifications();
switch (result) {
case Ok<Map<String, dynamic>>():
final response = result.value;
_hasUnreadNotification = response['success'] == true;
break;
case Error<Map<String, dynamic>>():
_hasUnreadNotification = false;
break;
}
notifyListeners();
}
}
+2 -2
View File
@@ -45,9 +45,9 @@ class NotificationViewModel with ChangeNotifier {
/// Fetch notifications
Future<void> getNotifications({int page = 1, bool isMobile = false}) async {
if (isMobile && page > 1) {
_isMoreLoading = true; // 👈 لودینگ فقط برای پیج بعدی
_isMoreLoading = true;
} else {
_isLoading = true; // 👈 لودینگ عادی (اولین بار یا رفرش)
_isLoading = true;
}
notifyListeners();
+51 -30
View File
@@ -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<HomeViewModel>().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,
),
],
),
),
],
),
);
}
),
);
}
}
+21 -1
View File
@@ -129,6 +129,8 @@ class TabletNavigationWidget extends StatelessWidget {
required String iconPath,
required String activeIconPath,
}) {
final hasUnread = context.watch<HomeViewModel>().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,