merge with conflict
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user