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,
@@ -95,9 +99,26 @@ class MobileShellPage extends StatelessWidget {
padding: EdgeInsets.only(top: 12.0.h(), bottom: 14.0.h()),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
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();
@@ -148,14 +148,16 @@ class DesktopNavigationWidget extends StatelessWidget {
);
}
Widget _navigationItem({
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(
@@ -164,8 +166,26 @@ class DesktopNavigationWidget extends StatelessWidget {
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,
@@ -179,5 +199,6 @@ class DesktopNavigationWidget extends StatelessWidget {
),
),
);
}
}
}
@@ -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(
@@ -138,8 +140,26 @@ class TabletNavigationWidget extends StatelessWidget {
child: Padding(
padding: EdgeInsetsDirectional.only(start: 24.0.w()),
child: Row(
children: [
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,