Update .gitignore to exclude Firebase configuration files and refactor notification handling in the app. Removed redundant logging and notification data storage in SharedPreferences. Introduced NotificationCheckService and NotificationStateService for improved notification management. Updated view models and UI components to utilize the new services, enhancing notification state tracking and user experience.
This commit is contained in:
@@ -3,11 +3,14 @@ import 'package:tm_app/data/services/model/customer/customer.dart';
|
||||
import 'package:tm_app/data/services/model/forgot_password_response/forgot_password_response_model.dart';
|
||||
import 'package:tm_app/data/services/model/verify_otp_response/verify_otp_response_model.dart';
|
||||
|
||||
import '../core/utils/error_utils.dart';
|
||||
import '../core/utils/logger.dart';
|
||||
import '../core/utils/result.dart';
|
||||
import '../data/repositories/auth_repository.dart';
|
||||
import '../data/services/model/login_response/login_response_model.dart';
|
||||
import '../data/services/model/logout_response/logout_response.dart';
|
||||
import '../data/services/model/reset_password_response/reset_password_response.dart';
|
||||
import '../views/forget_password_create/strings/forgot_password_create_strings.dart';
|
||||
|
||||
class AuthViewModel with ChangeNotifier {
|
||||
final AuthRepository _authRepository;
|
||||
@@ -139,9 +142,6 @@ class AuthViewModel with ChangeNotifier {
|
||||
}
|
||||
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
|
||||
// reset error after notify
|
||||
_errorMessage = null;
|
||||
notifyListeners();
|
||||
}
|
||||
@@ -151,6 +151,7 @@ class AuthViewModel with ChangeNotifier {
|
||||
await _authRepository.localLogout();
|
||||
} catch (e) {
|
||||
// Handle local logout exceptions gracefully
|
||||
AppLogger().error('❌ Failed to local logout: $e');
|
||||
}
|
||||
_loggedInUser = null;
|
||||
notifyListeners();
|
||||
@@ -177,7 +178,7 @@ class AuthViewModel with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> forgotPassword(BuildContext context) async {
|
||||
Future<void> forgotPassword() async {
|
||||
_isLoadingForgot = true;
|
||||
_errorMessageForget = null;
|
||||
successMessage = null;
|
||||
@@ -214,7 +215,7 @@ class AuthViewModel with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> verifyOtp(BuildContext context) async {
|
||||
Future<void> verifyOtp() async {
|
||||
_isLoadingOtp = true;
|
||||
_errorMessageOtp = null;
|
||||
_verifyOtpSuccess = false;
|
||||
@@ -273,16 +274,16 @@ class AuthViewModel with ChangeNotifier {
|
||||
_resetPasswordSuccess = true;
|
||||
break;
|
||||
case Error<ResetPasswordResponse>():
|
||||
_resetPasswordErrorMessage = result.error.toString();
|
||||
_resetPasswordErrorMessage = ErrorUtils.getErrorMessage(result.error);
|
||||
_resetPasswordSuccess = false;
|
||||
break;
|
||||
}
|
||||
_isResetPasswordLoading = false;
|
||||
notifyListeners();
|
||||
_resetPasswordErrorMessage = null;
|
||||
notifyListeners();
|
||||
} else {
|
||||
_resetPasswordErrorMessage = 'Reset token is required';
|
||||
_resetPasswordErrorMessage =
|
||||
ForgotPasswordCreateStrings.passwordResetFailed;
|
||||
_isResetPasswordLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
@@ -325,7 +326,6 @@ class AuthViewModel with ChangeNotifier {
|
||||
emailController.dispose();
|
||||
newPasswordController.dispose();
|
||||
otpController.dispose();
|
||||
|
||||
usernameFocus.dispose();
|
||||
passwordFocus.dispose();
|
||||
emailFocus.dispose();
|
||||
|
||||
@@ -10,15 +10,31 @@ import '../data/repositories/home_repository.dart';
|
||||
import '../data/services/model/feedback_stats_response/feedback_stat_response.dart';
|
||||
import '../data/services/model/home/home_response/home_response_model.dart';
|
||||
import '../data/services/model/request_models/get_tenders_request_model/get_tenders_request_model.dart';
|
||||
import '../data/services/notification_state_service.dart';
|
||||
|
||||
class HomeViewModel with ChangeNotifier {
|
||||
final HomeRepository _homeRepository;
|
||||
final NotificationStateService _notificationStateService;
|
||||
|
||||
HomeViewModel({required HomeRepository homeRepository})
|
||||
: _homeRepository = homeRepository {
|
||||
HomeViewModel({
|
||||
required HomeRepository homeRepository,
|
||||
required NotificationStateService notificationStateService,
|
||||
}) : _homeRepository = homeRepository,
|
||||
_notificationStateService = notificationStateService {
|
||||
_notificationStateService.addListener(_onNotificationStateChanged);
|
||||
init();
|
||||
}
|
||||
|
||||
void _onNotificationStateChanged() {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_notificationStateService.removeListener(_onNotificationStateChanged);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
bool _isLoading = false;
|
||||
String? _errorMessage;
|
||||
HomeResponseModel? _homeResponse;
|
||||
@@ -44,8 +60,8 @@ class HomeViewModel with ChangeNotifier {
|
||||
bool get hasMore => _hasMore;
|
||||
bool get isRecommendedMode => _isRecommendedMode;
|
||||
|
||||
bool _hasUnreadNotification = false;
|
||||
bool get hasUnreadNotification => _hasUnreadNotification;
|
||||
bool get hasUnreadNotification =>
|
||||
_notificationStateService.hasUnreadNotification;
|
||||
|
||||
void init() async {
|
||||
_isLoading = true;
|
||||
@@ -54,7 +70,6 @@ class HomeViewModel with ChangeNotifier {
|
||||
await getApprovedStates();
|
||||
await getYourTenders(reset: true);
|
||||
await getFeedbackStats();
|
||||
await checkUnreadNotifications();
|
||||
|
||||
if (_errorMessage != null) {
|
||||
_isLoading = false;
|
||||
@@ -212,32 +227,4 @@ 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;
|
||||
|
||||
final data = response['data'];
|
||||
if (data is List && data.isNotEmpty) {
|
||||
_hasUnreadNotification = true;
|
||||
} else {
|
||||
_hasUnreadNotification = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case Error<Map<String, dynamic>>():
|
||||
_hasUnreadNotification = false;
|
||||
break;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setUnreadNotificationFalse() {
|
||||
_hasUnreadNotification = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import 'package:tm_app/data/repositories/liked_tenders_repository.dart';
|
||||
import 'package:tm_app/data/repositories/tenders_repository.dart';
|
||||
import 'package:tm_app/data/services/model/liked_tender_feedback/liked_tender_feedback.dart';
|
||||
import 'package:tm_app/data/services/model/liked_tenders_response/liked_tenders_response.dart';
|
||||
import 'package:tm_app/view_models/home_view_model.dart';
|
||||
|
||||
import '../data/services/model/feedback_response/feedback_response.dart';
|
||||
import '../data/services/model/request_models/tender_feedback_request_model/tender_feedback_request_model.dart';
|
||||
@@ -12,13 +11,12 @@ import '../data/services/model/request_models/tender_feedback_request_model/tend
|
||||
class LikedTendersViewModel with ChangeNotifier {
|
||||
final LikedTendersRepository _likedTendersRepository;
|
||||
final TendersRepository _tendersRepository;
|
||||
final HomeViewModel _homeViewModel;
|
||||
|
||||
LikedTendersViewModel({
|
||||
required LikedTendersRepository likedTendersRepository,
|
||||
required TendersRepository tendersRepository,
|
||||
required HomeViewModel homeViewModel,
|
||||
}) : _likedTendersRepository = likedTendersRepository,
|
||||
_homeViewModel = homeViewModel,
|
||||
|
||||
_tendersRepository = tendersRepository;
|
||||
|
||||
bool isLoading = false;
|
||||
@@ -44,10 +42,6 @@ class LikedTendersViewModel with ChangeNotifier {
|
||||
bool get isSubmitApprovalLoading => _isSubmitApprovalLoading;
|
||||
bool get isRejectApprovalLoading => _isRejectApprovalLoading;
|
||||
|
||||
void updateHome() {
|
||||
_homeViewModel.init();
|
||||
}
|
||||
|
||||
void clearApprovalErrorMessage() {
|
||||
_errorMessage = null;
|
||||
notifyListeners();
|
||||
|
||||
@@ -4,18 +4,22 @@ import 'package:tm_app/core/utils/app_toast.dart';
|
||||
import 'package:tm_app/core/utils/result.dart';
|
||||
import 'package:tm_app/data/repositories/notification_repository.dart';
|
||||
import 'package:tm_app/data/services/model/notification__response/notification_response_model.dart';
|
||||
import 'package:tm_app/view_models/home_view_model.dart';
|
||||
import 'package:tm_app/data/services/notification_check_service.dart';
|
||||
import 'package:tm_app/data/services/notification_state_service.dart';
|
||||
import 'package:tm_app/views/notification/strings/notification_strings.dart';
|
||||
|
||||
class NotificationViewModel with ChangeNotifier {
|
||||
final NotificationsRepository _repository;
|
||||
final HomeViewModel _homeViewModel;
|
||||
final NotificationStateService _notificationStateService;
|
||||
final NotificationCheckService _notificationCheckService;
|
||||
|
||||
NotificationViewModel({
|
||||
required NotificationsRepository repositoryViewModel,
|
||||
required HomeViewModel homeViewModel,
|
||||
}) : _repository = repositoryViewModel,
|
||||
_homeViewModel = homeViewModel {
|
||||
required NotificationsRepository notificationsRepository,
|
||||
required NotificationStateService notificationStateService,
|
||||
required NotificationCheckService notificationCheckService,
|
||||
}) : _repository = notificationsRepository,
|
||||
_notificationStateService = notificationStateService,
|
||||
_notificationCheckService = notificationCheckService {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
init();
|
||||
});
|
||||
@@ -347,7 +351,7 @@ class NotificationViewModel with ChangeNotifier {
|
||||
}
|
||||
|
||||
void setNotificationFalse(BuildContext context) {
|
||||
_homeViewModel.setUnreadNotificationFalse();
|
||||
_notificationStateService.clearUnreadNotification();
|
||||
}
|
||||
|
||||
void _checkAndUpdateUnreadState() {
|
||||
@@ -379,8 +383,9 @@ class NotificationViewModel with ChangeNotifier {
|
||||
return importantCount > 0;
|
||||
}
|
||||
|
||||
void checkNotifications() {
|
||||
_homeViewModel.checkUnreadNotifications();
|
||||
Future<void> checkNotifications() async {
|
||||
// Re-check unread notifications from server
|
||||
await _notificationCheckService.checkUnreadNotifications();
|
||||
}
|
||||
|
||||
Future<void> markAsRead({required String notificationId}) async {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:tm_app/data/repositories/auth_repository.dart';
|
||||
import 'package:tm_app/data/services/model/company_profile_data/company_profile_data.dart';
|
||||
import 'package:tm_app/data/services/model/company_profile_response/company_profile_response.dart';
|
||||
@@ -30,65 +29,12 @@ class ProfileViewModel with ChangeNotifier {
|
||||
CompanyProfileData? _companyProfileData;
|
||||
bool _loggedOut = false;
|
||||
|
||||
// Notification info saved from FCM message in SharedPreferences
|
||||
String? _notifData;
|
||||
String? _notifFrom;
|
||||
String? _notifType;
|
||||
String? _notifKeys;
|
||||
String? _notifTitle;
|
||||
String? _notifBody;
|
||||
String? _notifWebLink;
|
||||
String? _notifAndroidLink;
|
||||
String? _notifWebImage;
|
||||
String? _notifAndroidImage;
|
||||
String? _notifWebClickAction;
|
||||
|
||||
bool get isLoading => _isLoading;
|
||||
bool get loggedOut => _loggedOut;
|
||||
String? get errorMessage => _errorMessage;
|
||||
ProfileData? get profileData => _profileData;
|
||||
CompanyProfileData? get companyProfileData => _companyProfileData;
|
||||
|
||||
// Expose notification info
|
||||
String? get notifData => _notifData;
|
||||
String? get notifFrom => _notifFrom;
|
||||
String? get notifType => _notifType;
|
||||
String? get notifKeys => _notifKeys;
|
||||
String? get notifTitle => _notifTitle;
|
||||
String? get notifBody => _notifBody;
|
||||
String? get notifWebLink => _notifWebLink;
|
||||
String? get notifAndroidLink => _notifAndroidLink;
|
||||
String? get notifWebImage => _notifWebImage;
|
||||
String? get notifAndroidImage => _notifAndroidImage;
|
||||
String? get notifWebClickAction => _notifWebClickAction;
|
||||
|
||||
String get notifSummaryText {
|
||||
final parts = <String>[];
|
||||
if (_notifTitle != null && _notifTitle!.isNotEmpty)
|
||||
parts.add('Title: ${_notifTitle!}');
|
||||
if (_notifBody != null && _notifBody!.isNotEmpty)
|
||||
parts.add('Body: ${_notifBody!}');
|
||||
if (_notifType != null && _notifType!.isNotEmpty)
|
||||
parts.add('Type: ${_notifType!}');
|
||||
if (_notifFrom != null && _notifFrom!.isNotEmpty)
|
||||
parts.add('From: ${_notifFrom!}');
|
||||
if (_notifKeys != null && _notifKeys!.isNotEmpty)
|
||||
parts.add('Keys: ${_notifKeys!}');
|
||||
if (_notifWebLink != null && _notifWebLink!.isNotEmpty)
|
||||
parts.add('Web: ${_notifWebLink!}');
|
||||
if (_notifAndroidLink != null && _notifAndroidLink!.isNotEmpty)
|
||||
parts.add('Android: ${_notifAndroidLink!}');
|
||||
if (_notifWebImage != null && _notifWebImage!.isNotEmpty)
|
||||
parts.add('WebImg: ${_notifWebImage!}');
|
||||
if (_notifAndroidImage != null && _notifAndroidImage!.isNotEmpty)
|
||||
parts.add('AndroidImg: ${_notifAndroidImage!}');
|
||||
if (_notifWebClickAction != null && _notifWebClickAction!.isNotEmpty)
|
||||
parts.add('Click: ${_notifWebClickAction!}');
|
||||
if (_notifData != null && _notifData!.isNotEmpty)
|
||||
parts.add('Data: ${_notifData!}');
|
||||
return parts.isEmpty ? '-' : parts.join(' | ');
|
||||
}
|
||||
|
||||
Future<void> getProfile() async {
|
||||
_isLoading = true;
|
||||
_errorMessage = null;
|
||||
@@ -152,28 +98,4 @@ class ProfileViewModel with ChangeNotifier {
|
||||
_loggedOut = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> loadNotificationInfoFromPrefs() async {
|
||||
// Do not toggle global loading; this is a lightweight local load
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
_notifData = prefs.getString('notif_data');
|
||||
_notifFrom = prefs.getString('notif_from');
|
||||
_notifType = prefs.getString('notif_type');
|
||||
_notifKeys = prefs.getString('notif_keys');
|
||||
_notifTitle = prefs.getString('notif_title');
|
||||
_notifBody = prefs.getString('notif_body');
|
||||
_notifWebLink = prefs.getString('notif_web_link');
|
||||
_notifAndroidLink = prefs.getString('notif_android_link');
|
||||
_notifWebImage = prefs.getString('notif_web_image');
|
||||
_notifAndroidImage = prefs.getString('notif_android_image');
|
||||
_notifWebClickAction = prefs.getString('notif_web_click_action');
|
||||
} catch (e) {
|
||||
// Non-fatal; surface as errorMessage so UI can toast if listening
|
||||
_errorMessage = e.toString();
|
||||
}
|
||||
notifyListeners();
|
||||
_errorMessage = null;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,23 +9,18 @@ import '../core/constants/tender_approval_status.dart';
|
||||
import '../data/services/model/tender_approvals_by_id_response/tender_approvals_by_id_response.dart';
|
||||
import '../data/services/model/tender_approvals_data/tender_approvals_data.dart';
|
||||
import '../data/services/model/tender_approvals_toggle_response/tender_approvals_toggle_response.dart';
|
||||
import 'home_view_model.dart';
|
||||
import 'tenders_view_model.dart';
|
||||
|
||||
class TenderDetailViewModel with ChangeNotifier {
|
||||
final TenderDetailRepository _tenderDetailRepository;
|
||||
final TendersRepository _tendersRepository;
|
||||
final HomeViewModel _homeViewModel;
|
||||
|
||||
TenderDetailViewModel({
|
||||
required TenderDetailRepository tenderDetailRepository,
|
||||
required TendersRepository tendersRepository,
|
||||
required TendersViewModel tendersViewModel,
|
||||
required HomeViewModel homeViewModel,
|
||||
}) : _tenderDetailRepository = tenderDetailRepository,
|
||||
_tendersRepository = tendersRepository,
|
||||
|
||||
_homeViewModel = homeViewModel;
|
||||
_tendersRepository = tendersRepository;
|
||||
|
||||
bool _isLoading = false;
|
||||
bool _isSubmitApprovalLoading = false;
|
||||
@@ -122,10 +117,6 @@ class TenderDetailViewModel with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void updateHome() async {
|
||||
_homeViewModel.init();
|
||||
}
|
||||
|
||||
Future<void> getTenderApprovals(String tenderId) async {
|
||||
_errorMessage = null;
|
||||
notifyListeners();
|
||||
|
||||
@@ -9,15 +9,12 @@ import 'package:tm_app/data/services/model/tenders/tenders_response/tenders_resp
|
||||
import 'package:tm_app/views/tenders/strings/tenders_strings.dart';
|
||||
|
||||
import '../core/utils/result.dart';
|
||||
import 'home_view_model.dart';
|
||||
|
||||
class TendersViewModel with ChangeNotifier {
|
||||
final TendersRepository _tendersRepository;
|
||||
|
||||
TendersViewModel({
|
||||
required TendersRepository tendersRepository,
|
||||
required HomeViewModel homeViewModel,
|
||||
}) : _tendersRepository = tendersRepository;
|
||||
TendersViewModel({required TendersRepository tendersRepository})
|
||||
: _tendersRepository = tendersRepository;
|
||||
|
||||
bool _isLoading = false;
|
||||
bool _isLoadingMore = false;
|
||||
|
||||
Reference in New Issue
Block a user