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:
@@ -10,7 +10,9 @@ import 'package:tm_app/data/repositories/tenders_repository.dart';
|
||||
import 'package:tm_app/data/repositories/your_tenders_repository.dart';
|
||||
import 'package:tm_app/data/services/home_service.dart';
|
||||
import 'package:tm_app/data/services/liked_tenders_service.dart';
|
||||
import 'package:tm_app/data/services/notification_check_service.dart';
|
||||
import 'package:tm_app/data/services/notification_service.dart';
|
||||
import 'package:tm_app/data/services/notification_state_service.dart';
|
||||
import 'package:tm_app/data/services/tender_detail_service.dart';
|
||||
import 'package:tm_app/data/services/your_tenders_service.dart';
|
||||
import 'package:tm_app/view_models/final_completion_of_documents_view_model.dart';
|
||||
@@ -30,13 +32,14 @@ import '../../view_models/home_view_model.dart';
|
||||
import '../../view_models/profile_view_model.dart';
|
||||
|
||||
List<SingleChildWidget> get providersRemote {
|
||||
return [...cores, ...apiClients, ...repositories, ...viewModels, ...others];
|
||||
return [...cores, ...apiClients, ...repositories, ...services, ...viewModels];
|
||||
}
|
||||
|
||||
List<SingleChildWidget> get cores {
|
||||
return [
|
||||
Provider(create: (context) => NetworkManager(context.read())),
|
||||
ChangeNotifierProvider(create: (context) => ThemeProvider()),
|
||||
ChangeNotifierProvider(create: (context) => NotificationStateService()),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -108,20 +111,32 @@ List<SingleChildWidget> get repositories {
|
||||
];
|
||||
}
|
||||
|
||||
List<SingleChildWidget> get services {
|
||||
return [
|
||||
Provider(
|
||||
create:
|
||||
(context) => NotificationCheckService(
|
||||
homeRepository: context.read(),
|
||||
notificationStateService: context.read(),
|
||||
),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
List<SingleChildWidget> get viewModels {
|
||||
return [
|
||||
ChangeNotifierProvider(
|
||||
create: (context) => AuthViewModel(authRepository: context.read()),
|
||||
),
|
||||
ChangeNotifierProvider(
|
||||
create: (context) => HomeViewModel(homeRepository: context.read()),
|
||||
create:
|
||||
(context) => HomeViewModel(
|
||||
homeRepository: context.read(),
|
||||
notificationStateService: context.read(),
|
||||
),
|
||||
),
|
||||
ChangeNotifierProvider(
|
||||
create:
|
||||
(context) => TendersViewModel(
|
||||
tendersRepository: context.read(),
|
||||
homeViewModel: context.read(),
|
||||
),
|
||||
create: (context) => TendersViewModel(tendersRepository: context.read()),
|
||||
),
|
||||
ChangeNotifierProvider(
|
||||
create:
|
||||
@@ -129,7 +144,6 @@ List<SingleChildWidget> get viewModels {
|
||||
tenderDetailRepository: context.read(),
|
||||
tendersRepository: context.read(),
|
||||
tendersViewModel: context.read(),
|
||||
homeViewModel: context.read(),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -143,7 +157,6 @@ List<SingleChildWidget> get viewModels {
|
||||
(context) => LikedTendersViewModel(
|
||||
likedTendersRepository: context.read(),
|
||||
tendersRepository: context.read(),
|
||||
homeViewModel: context.read(),
|
||||
),
|
||||
),
|
||||
ChangeNotifierProvider(
|
||||
@@ -160,13 +173,10 @@ List<SingleChildWidget> get viewModels {
|
||||
ChangeNotifierProvider(
|
||||
create:
|
||||
(context) => NotificationViewModel(
|
||||
repositoryViewModel: context.read(),
|
||||
homeViewModel: context.read(),
|
||||
notificationsRepository: context.read(),
|
||||
notificationStateService: context.read(),
|
||||
notificationCheckService: context.read(),
|
||||
),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
List<SingleChildWidget> get others {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -4,26 +4,41 @@ import 'package:provider/provider.dart';
|
||||
import 'package:tm_app/core/routes/app_shell/desktop_shell_page.dart';
|
||||
import 'package:tm_app/core/routes/app_shell/mobile_shell_page.dart';
|
||||
import 'package:tm_app/core/routes/app_shell/tablet_shell_page.dart';
|
||||
import 'package:tm_app/data/services/notification_check_service.dart';
|
||||
import 'package:tm_app/view_models/home_view_model.dart';
|
||||
import 'package:tm_app/view_models/tenders_view_model.dart';
|
||||
import 'package:tm_app/views/shared/responsive_builder.dart';
|
||||
|
||||
import '../../../view_models/notification_view_model.dart';
|
||||
|
||||
class AppShellScreen extends StatelessWidget {
|
||||
class AppShellScreen extends StatefulWidget {
|
||||
const AppShellScreen({required this.navigationShell, super.key});
|
||||
|
||||
final StatefulNavigationShell navigationShell;
|
||||
|
||||
@override
|
||||
State<AppShellScreen> createState() => _AppShellScreenState();
|
||||
}
|
||||
|
||||
class _AppShellScreenState extends State<AppShellScreen> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Check for unread notifications when app shell loads
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
context.read<NotificationCheckService>().checkUnreadNotifications();
|
||||
});
|
||||
}
|
||||
|
||||
void _gotoBranch(int index, BuildContext context) {
|
||||
if (index == navigationShell.currentIndex) {
|
||||
if (index == widget.navigationShell.currentIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Navigate first
|
||||
navigationShell.goBranch(
|
||||
widget.navigationShell.goBranch(
|
||||
index,
|
||||
initialLocation: index == navigationShell.currentIndex,
|
||||
initialLocation: index == widget.navigationShell.currentIndex,
|
||||
);
|
||||
|
||||
// Then trigger data loading after navigation is complete
|
||||
@@ -45,16 +60,16 @@ class AppShellScreen extends StatelessWidget {
|
||||
return ResponsiveBuilder(
|
||||
mobile: SafeArea(
|
||||
child: MobileShellPage(
|
||||
navigationShell: navigationShell,
|
||||
navigationShell: widget.navigationShell,
|
||||
onTap: (value) => _gotoBranch(value, context),
|
||||
),
|
||||
),
|
||||
tablet: TabletShellPage(
|
||||
navigationShell: navigationShell,
|
||||
navigationShell: widget.navigationShell,
|
||||
onTap: (value) => _gotoBranch(value, context),
|
||||
),
|
||||
desktop: DesktopShellPage(
|
||||
navigationShell: navigationShell,
|
||||
navigationShell: widget.navigationShell,
|
||||
onTap: (value) => _gotoBranch(value, context),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -5,9 +5,9 @@ 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 '../../../data/services/notification_state_service.dart';
|
||||
import '../../../views/home/strings/home_strings.dart';
|
||||
import '../../../views/tenders/strings/tenders_strings.dart';
|
||||
|
||||
@@ -65,7 +65,10 @@ class MobileShellPage extends StatelessWidget {
|
||||
onTap: () => onTap(3),
|
||||
iconPath: AssetsManager.notify,
|
||||
activeIconPath: AssetsManager.notifyActive,
|
||||
showBadge: context.watch<HomeViewModel>().hasUnreadNotification,
|
||||
showBadge:
|
||||
context
|
||||
.watch<NotificationStateService>()
|
||||
.hasUnreadNotification,
|
||||
),
|
||||
_bottomNavigationItem(
|
||||
context: context,
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import 'package:tm_app/core/utils/result.dart';
|
||||
import 'package:tm_app/data/repositories/home_repository.dart';
|
||||
import 'package:tm_app/data/services/notification_state_service.dart';
|
||||
|
||||
/// Service to check for unread notifications and update state
|
||||
class NotificationCheckService {
|
||||
final HomeRepository _homeRepository;
|
||||
final NotificationStateService _notificationStateService;
|
||||
|
||||
NotificationCheckService({
|
||||
required HomeRepository homeRepository,
|
||||
required NotificationStateService notificationStateService,
|
||||
}) : _homeRepository = homeRepository,
|
||||
_notificationStateService = notificationStateService;
|
||||
|
||||
Future<void> checkUnreadNotifications() async {
|
||||
final result = await _homeRepository.checkUnreadNotifications();
|
||||
|
||||
switch (result) {
|
||||
case Ok<Map<String, dynamic>>():
|
||||
final response = result.value;
|
||||
final data = response['data'];
|
||||
_notificationStateService.setHasUnreadNotification(
|
||||
data is List && data.isNotEmpty,
|
||||
);
|
||||
break;
|
||||
case Error<Map<String, dynamic>>():
|
||||
_notificationStateService.setHasUnreadNotification(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// Service to manage notification state across the app
|
||||
class NotificationStateService extends ChangeNotifier {
|
||||
bool _hasUnreadNotification = false;
|
||||
|
||||
bool get hasUnreadNotification => _hasUnreadNotification;
|
||||
|
||||
void setHasUnreadNotification(bool value) {
|
||||
if (_hasUnreadNotification != value) {
|
||||
_hasUnreadNotification = value;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
void clearUnreadNotification() => setHasUnreadNotification(false);
|
||||
}
|
||||
@@ -66,45 +66,9 @@ class _MyAppState extends State<MyApp> {
|
||||
// if (kIsWeb) {
|
||||
FirebaseMessaging.onMessage.listen((event) {
|
||||
final overlayCtx = rootNavigatorKey.currentContext;
|
||||
AppLogger().error('Notification received: ${event.notification?.web}');
|
||||
AppLogger().error('web link: ${event.notification?.title}');
|
||||
AppLogger().error('web link: ${event.notification?.web?.link}');
|
||||
print('Notification received1: ${event.notification?.web.toString()}');
|
||||
print(
|
||||
'Notification received1: ${event.notification?.web?.analyticsLabel}',
|
||||
);
|
||||
|
||||
print('web link: ${event.notification?.web?.link}');
|
||||
print('Notification received: ${event.notification?.title}');
|
||||
AppLogger().error('notif data: ${event.data}');
|
||||
AppLogger().error('notif from: ${event.from}');
|
||||
AppLogger().error('notif type: ${event.messageType}');
|
||||
AppLogger().error('notif keys: ${event.data.keys}');
|
||||
AppLogger().info('notif title: ${event.notification?.title}');
|
||||
AppLogger().info('notif body: ${event.notification?.body}');
|
||||
AppLogger().info('notif data: ${event.data}');
|
||||
AppLogger().info('notif from: ${event.from}');
|
||||
AppLogger().info('notif type: ${event.messageType}');
|
||||
AppLogger().info('notif keys: ${event.data.keys}');
|
||||
if (overlayCtx != null) {
|
||||
if (overlayCtx.mounted) {
|
||||
AppLogger().error(
|
||||
'Notification received: ${event.notification?.web}',
|
||||
);
|
||||
AppLogger().error(
|
||||
' link android: ${event.notification?.android?.link}',
|
||||
);
|
||||
AppLogger().error(
|
||||
'android link image: ${event.notification?.android?.imageUrl}',
|
||||
);
|
||||
AppLogger().error(
|
||||
'android link click action: ${event.notification?.android?.clickAction}',
|
||||
);
|
||||
AppLogger().error(
|
||||
'web link image: ${event.notification?.web?.image}',
|
||||
);
|
||||
print('Notification received: ${event.notification?.web}');
|
||||
print('web link: ${event.notification?.web?.link}');
|
||||
AppToast.notification(
|
||||
overlayCtx,
|
||||
event.notification?.title ?? 'New Notification',
|
||||
@@ -119,32 +83,6 @@ class _MyAppState extends State<MyApp> {
|
||||
}
|
||||
|
||||
void _handleMessage(RemoteMessage message) {
|
||||
SharedPreferences.getInstance().then((value) {
|
||||
value.setString('notif_data', message.data.toString());
|
||||
value.setString('notif_from', message.from.toString());
|
||||
value.setString('notif_type', message.messageType.toString());
|
||||
value.setString('notif_keys', message.data.keys.toString());
|
||||
value.setString('notif_title', message.notification?.title ?? '');
|
||||
value.setString('notif_body', message.notification?.body ?? '');
|
||||
value.setString('notif_data', message.contentAvailable.toString());
|
||||
value.setString('notif_web_link', message.notification?.web?.link ?? '');
|
||||
value.setString(
|
||||
'notif_android_link',
|
||||
message.notification?.android?.link ?? '',
|
||||
);
|
||||
value.setString(
|
||||
'notif_web_image',
|
||||
message.notification?.web?.image ?? '',
|
||||
);
|
||||
value.setString(
|
||||
'notif_android_image',
|
||||
message.notification?.android?.imageUrl ?? '',
|
||||
);
|
||||
value.setString(
|
||||
'notif_web_click_action',
|
||||
message.notification?.web?.link ?? '',
|
||||
);
|
||||
});
|
||||
appRouter.go(
|
||||
const SplashScreenRoute(navigateToNotification: true).location,
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -13,4 +13,5 @@ class ForgotPasswordCreateStrings {
|
||||
static const String atLeastSpecial =
|
||||
'• At least one special character: ! @ # \$ % & * ^';
|
||||
static const String passwordResetSuccess = 'Password reset successfully';
|
||||
static const String passwordResetFailed = 'Reset Password failed';
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ class _DesktopForgotPasswordOtpPageState
|
||||
onPressed:
|
||||
viewModel.canSubmitOtpPage
|
||||
? () async {
|
||||
await viewModel.verifyOtp(context);
|
||||
await viewModel.verifyOtp();
|
||||
}
|
||||
: null,
|
||||
),
|
||||
|
||||
@@ -122,7 +122,7 @@ class _MobileForgotPasswordOtpPageState
|
||||
onPressed:
|
||||
viewModel.canSubmitOtpPage
|
||||
? () async {
|
||||
await viewModel.verifyOtp(context);
|
||||
await viewModel.verifyOtp();
|
||||
}
|
||||
: null,
|
||||
),
|
||||
|
||||
@@ -127,7 +127,7 @@ class _TabletForgotPasswordOtpPageState
|
||||
onPressed:
|
||||
viewModel.canSubmitOtpPage
|
||||
? () async {
|
||||
await viewModel.verifyOtp(context);
|
||||
await viewModel.verifyOtp();
|
||||
}
|
||||
: null,
|
||||
),
|
||||
|
||||
@@ -117,9 +117,7 @@ class _DesktopForgotPasswordPageState extends State<DesktopForgotPasswordPage> {
|
||||
onPressed:
|
||||
viewModel.isEmailValid
|
||||
? () async {
|
||||
await viewModel.forgotPassword(
|
||||
context,
|
||||
);
|
||||
await viewModel.forgotPassword();
|
||||
}
|
||||
: null,
|
||||
),
|
||||
|
||||
@@ -110,7 +110,7 @@ class _MobileForgotPasswordPageState extends State<MobileForgotPasswordPage> {
|
||||
onPressed:
|
||||
viewModel.isEmailValid
|
||||
? () async {
|
||||
await viewModel.forgotPassword(context);
|
||||
await viewModel.forgotPassword();
|
||||
}
|
||||
: null,
|
||||
),
|
||||
|
||||
@@ -111,7 +111,7 @@ class _TabletForgotPasswordPageState extends State<TabletForgotPasswordPage> {
|
||||
onPressed:
|
||||
viewModel.isEmailValid
|
||||
? () async {
|
||||
await viewModel.forgotPassword(context);
|
||||
await viewModel.forgotPassword();
|
||||
}
|
||||
: null,
|
||||
),
|
||||
|
||||
@@ -49,83 +49,76 @@ class _LikedTendersDesktopPageState extends State<LikedTendersDesktopPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopScope(
|
||||
onPopInvokedWithResult: (didPop, result) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
viewModel.updateHome();
|
||||
});
|
||||
},
|
||||
child: Scaffold(
|
||||
key: scaffoldKey,
|
||||
backgroundColor: AppColors.backgroundColor,
|
||||
endDrawer: const LikeFiltersDrawer(),
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
const DesktopNavigationWidget(currentIndex: 1, haveFilter: true),
|
||||
const SizedBox(height: 40),
|
||||
SizedBox(
|
||||
return Scaffold(
|
||||
key: scaffoldKey,
|
||||
backgroundColor: AppColors.backgroundColor,
|
||||
endDrawer: const LikeFiltersDrawer(),
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
const DesktopNavigationWidget(currentIndex: 1, haveFilter: true),
|
||||
const SizedBox(height: 40),
|
||||
SizedBox(
|
||||
width: 740,
|
||||
child: TabletDesktopAppbar(
|
||||
title: LikedTendersStrings.likedTenders,
|
||||
haveFilter: true,
|
||||
onFilterPressed: () {
|
||||
scaffoldKey.currentState?.openEndDrawer();
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
width: 740,
|
||||
child: TabletDesktopAppbar(
|
||||
title: LikedTendersStrings.likedTenders,
|
||||
haveFilter: true,
|
||||
onFilterPressed: () {
|
||||
scaffoldKey.currentState?.openEndDrawer();
|
||||
child: Consumer<LikedTendersViewModel>(
|
||||
builder: (context, viewModel, child) {
|
||||
if (viewModel.isLoading &&
|
||||
(viewModel.data?.data?.feedback ?? []).isEmpty) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: AppColors.secondary50,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (viewModel.errorMessage != null) {
|
||||
return Center(child: Text(viewModel.errorMessage!));
|
||||
}
|
||||
|
||||
final feedback = viewModel.data?.data?.feedback ?? [];
|
||||
|
||||
if (feedback.isEmpty) {
|
||||
return const Center(
|
||||
child: Text(LikedTendersStrings.noLikedTenders),
|
||||
);
|
||||
}
|
||||
|
||||
final currentPage = viewModel.currentPage;
|
||||
final totalPages = viewModel.data?.data?.meta?.pages ?? 1;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
_likedTendersList(feedback, viewModel),
|
||||
|
||||
SizedBox(height: 10.0.h()),
|
||||
|
||||
_likedTenersListPageControll(
|
||||
context,
|
||||
totalPages,
|
||||
viewModel,
|
||||
currentPage,
|
||||
),
|
||||
|
||||
SizedBox(height: 30.0.h()),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
width: 740,
|
||||
child: Consumer<LikedTendersViewModel>(
|
||||
builder: (context, viewModel, child) {
|
||||
if (viewModel.isLoading &&
|
||||
(viewModel.data?.data?.feedback ?? []).isEmpty) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: AppColors.secondary50,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (viewModel.errorMessage != null) {
|
||||
return Center(child: Text(viewModel.errorMessage!));
|
||||
}
|
||||
|
||||
final feedback = viewModel.data?.data?.feedback ?? [];
|
||||
|
||||
if (feedback.isEmpty) {
|
||||
return const Center(
|
||||
child: Text(LikedTendersStrings.noLikedTenders),
|
||||
);
|
||||
}
|
||||
|
||||
final currentPage = viewModel.currentPage;
|
||||
final totalPages = viewModel.data?.data?.meta?.pages ?? 1;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
_likedTendersList(feedback, viewModel),
|
||||
|
||||
SizedBox(height: 10.0.h()),
|
||||
|
||||
_likedTenersListPageControll(
|
||||
context,
|
||||
totalPages,
|
||||
viewModel,
|
||||
currentPage,
|
||||
),
|
||||
|
||||
SizedBox(height: 30.0.h()),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -62,188 +61,179 @@ class _LikedTendersMobilePageState extends State<LikedTendersMobilePage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopScope(
|
||||
onPopInvokedWithResult: (didPop, result) {
|
||||
if (kIsWeb) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
viewModel.updateHome();
|
||||
});
|
||||
}
|
||||
},
|
||||
child: SafeArea(
|
||||
child: Scaffold(
|
||||
backgroundColor: AppColors.backgroundColor,
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(60),
|
||||
child: AppBar(
|
||||
backgroundColor: AppColors.backgroundColor,
|
||||
elevation: 0,
|
||||
titleSpacing: 0,
|
||||
leading: IconButton(
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
backgroundColor: AppColors.backgroundColor,
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(60),
|
||||
child: AppBar(
|
||||
backgroundColor: AppColors.backgroundColor,
|
||||
elevation: 0,
|
||||
titleSpacing: 0,
|
||||
leading: IconButton(
|
||||
icon: SvgPicture.asset(
|
||||
AssetsManager.arrowLeft,
|
||||
height: 24.0.w(),
|
||||
width: 24.0.w(),
|
||||
),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
title: Text(
|
||||
LikedTendersStrings.likedTenders,
|
||||
style: TextStyle(
|
||||
color: AppColors.grey70,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 20.0.sp(),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: SvgPicture.asset(
|
||||
AssetsManager.arrowLeft,
|
||||
AssetsManager.filter,
|
||||
height: 24.0.w(),
|
||||
width: 24.0.w(),
|
||||
),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
title: Text(
|
||||
LikedTendersStrings.likedTenders,
|
||||
style: TextStyle(
|
||||
color: AppColors.grey70,
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 20.0.sp(),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: SvgPicture.asset(
|
||||
AssetsManager.filter,
|
||||
height: 24.0.w(),
|
||||
width: 24.0.w(),
|
||||
),
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
backgroundColor: AppColors.backgroundColor,
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(20),
|
||||
),
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
backgroundColor: AppColors.backgroundColor,
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(20),
|
||||
),
|
||||
builder: (context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(height: 10.0.h()),
|
||||
Text(
|
||||
LikedTendersStrings.filter,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey70,
|
||||
fontSize: 20.0.sp(),
|
||||
),
|
||||
),
|
||||
builder: (context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(height: 10.0.h()),
|
||||
Text(
|
||||
LikedTendersStrings.filter,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.grey70,
|
||||
fontSize: 20.0.sp(),
|
||||
),
|
||||
SizedBox(height: 20.0.h()),
|
||||
_buildFilterOption('Industry'),
|
||||
_buildFilterOption('Finance'),
|
||||
_buildFilterOption('Business'),
|
||||
_buildFilterOption('IT'),
|
||||
_buildFilterOption('Software'),
|
||||
SizedBox(height: 20.0.h()),
|
||||
BaseButton(
|
||||
backgroundColor: AppColors.primary2,
|
||||
borderRadius: 24,
|
||||
isEnabled: true,
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
text: 'Apply',
|
||||
textColor: AppColors.textBlue,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
SizedBox(width: 16.0.w()),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
body: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Consumer<LikedTendersViewModel>(
|
||||
builder: (context, viewModel, child) {
|
||||
if (viewModel.isLoading &&
|
||||
(viewModel.data?.data?.feedback ?? []).isEmpty) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: AppColors.secondary50,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (viewModel.errorMessage != null) {
|
||||
return Center(child: Text(viewModel.errorMessage!));
|
||||
}
|
||||
|
||||
final feedback = viewModel.data?.data?.feedback ?? [];
|
||||
|
||||
if (feedback.isEmpty) {
|
||||
return const Center(
|
||||
child: Text(LikedTendersStrings.noLikedTenders),
|
||||
);
|
||||
}
|
||||
|
||||
final itemCount =
|
||||
feedback.length + (viewModel.isLoadingMore ? 1 : 0);
|
||||
|
||||
return ListView.builder(
|
||||
controller: _scrollController,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 24.0.w(),
|
||||
vertical: 15.0.h(),
|
||||
),
|
||||
itemCount: itemCount,
|
||||
itemBuilder: (context, index) {
|
||||
if (index < feedback.length) {
|
||||
final item = feedback[index];
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: 20.0.h()),
|
||||
child: Dismissible(
|
||||
key: ValueKey(item.tenderId ?? index),
|
||||
direction: DismissDirection.endToStart,
|
||||
background: Container(
|
||||
alignment: Alignment.centerRight,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 20.0.w(),
|
||||
),
|
||||
child: SvgPicture.asset(
|
||||
AssetsManager.trash,
|
||||
width: 32.0.w(),
|
||||
height: 32.0.h(),
|
||||
),
|
||||
),
|
||||
onDismissed: (_) {
|
||||
if (item.tenderId != null) {
|
||||
viewModel.removeTenderById(item.tenderId!);
|
||||
}
|
||||
),
|
||||
SizedBox(height: 20.0.h()),
|
||||
_buildFilterOption('Industry'),
|
||||
_buildFilterOption('Finance'),
|
||||
_buildFilterOption('Business'),
|
||||
_buildFilterOption('IT'),
|
||||
_buildFilterOption('Software'),
|
||||
SizedBox(height: 20.0.h()),
|
||||
BaseButton(
|
||||
backgroundColor: AppColors.primary2,
|
||||
borderRadius: 24,
|
||||
isEnabled: true,
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: LikedListItem(tender: item.tender!),
|
||||
text: 'Apply',
|
||||
textColor: AppColors.textBlue,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 20.0.h()),
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 24.0.w(),
|
||||
height: 24.0.h(),
|
||||
child: const CircularProgressIndicator(
|
||||
color: AppColors.secondary50,
|
||||
strokeWidth: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
SizedBox(width: 16.0.w()),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
body: Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Consumer<LikedTendersViewModel>(
|
||||
builder: (context, viewModel, child) {
|
||||
if (viewModel.isLoading &&
|
||||
(viewModel.data?.data?.feedback ?? []).isEmpty) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: AppColors.secondary50,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (viewModel.errorMessage != null) {
|
||||
return Center(child: Text(viewModel.errorMessage!));
|
||||
}
|
||||
|
||||
final feedback = viewModel.data?.data?.feedback ?? [];
|
||||
|
||||
if (feedback.isEmpty) {
|
||||
return const Center(
|
||||
child: Text(LikedTendersStrings.noLikedTenders),
|
||||
);
|
||||
}
|
||||
|
||||
final itemCount =
|
||||
feedback.length + (viewModel.isLoadingMore ? 1 : 0);
|
||||
|
||||
return ListView.builder(
|
||||
controller: _scrollController,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 24.0.w(),
|
||||
vertical: 15.0.h(),
|
||||
),
|
||||
itemCount: itemCount,
|
||||
itemBuilder: (context, index) {
|
||||
if (index < feedback.length) {
|
||||
final item = feedback[index];
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: 20.0.h()),
|
||||
child: Dismissible(
|
||||
key: ValueKey(item.tenderId ?? index),
|
||||
direction: DismissDirection.endToStart,
|
||||
background: Container(
|
||||
alignment: Alignment.centerRight,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 20.0.w(),
|
||||
),
|
||||
child: SvgPicture.asset(
|
||||
AssetsManager.trash,
|
||||
width: 32.0.w(),
|
||||
height: 32.0.h(),
|
||||
),
|
||||
),
|
||||
onDismissed: (_) {
|
||||
if (item.tenderId != null) {
|
||||
viewModel.removeTenderById(item.tenderId!);
|
||||
}
|
||||
},
|
||||
child: LikedListItem(tender: item.tender!),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 20.0.h()),
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 24.0.w(),
|
||||
height: 24.0.h(),
|
||||
child: const CircularProgressIndicator(
|
||||
color: AppColors.secondary50,
|
||||
strokeWidth: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -50,99 +49,89 @@ class _LikedTendersTabletPageState extends State<LikedTendersTabletPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final GlobalKey<ScaffoldState> key = GlobalKey();
|
||||
return PopScope(
|
||||
onPopInvokedWithResult: (didPop, result) {
|
||||
if (kIsWeb) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
viewModel.updateHome();
|
||||
});
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
return Scaffold(
|
||||
key: key,
|
||||
backgroundColor: AppColors.backgroundColor,
|
||||
appBar: tabletAppBar(
|
||||
title: LikedTendersStrings.likedTenders,
|
||||
key: key,
|
||||
backgroundColor: AppColors.backgroundColor,
|
||||
appBar: tabletAppBar(
|
||||
title: LikedTendersStrings.likedTenders,
|
||||
key: key,
|
||||
context: context,
|
||||
),
|
||||
drawer: const TabletNavigationWidget(currentIndex: 1),
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 720,
|
||||
child: Column(
|
||||
children: [
|
||||
TabletDesktopAppbar(
|
||||
title: LikedTendersStrings.likedTenders,
|
||||
haveFilter: true,
|
||||
onFilterPressed: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: AppColors.backgroundColor,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(20),
|
||||
context: context,
|
||||
),
|
||||
drawer: const TabletNavigationWidget(currentIndex: 1),
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 720,
|
||||
child: Column(
|
||||
children: [
|
||||
TabletDesktopAppbar(
|
||||
title: LikedTendersStrings.likedTenders,
|
||||
haveFilter: true,
|
||||
onFilterPressed: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: AppColors.backgroundColor,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.sizeOf(context).width,
|
||||
maxHeight: MediaQuery.sizeOf(context).height,
|
||||
),
|
||||
builder: (context) => const LikeFiltersBottomSheet(),
|
||||
);
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: Consumer<LikedTendersViewModel>(
|
||||
builder: (context, viewModel, child) {
|
||||
if (viewModel.isLoading &&
|
||||
(viewModel.data?.data?.feedback ?? []).isEmpty) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: AppColors.secondary50,
|
||||
),
|
||||
),
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.sizeOf(context).width,
|
||||
maxHeight: MediaQuery.sizeOf(context).height,
|
||||
),
|
||||
builder: (context) => const LikeFiltersBottomSheet(),
|
||||
);
|
||||
}
|
||||
|
||||
if (viewModel.errorMessage != null) {
|
||||
return Center(child: Text(viewModel.errorMessage!));
|
||||
}
|
||||
|
||||
final feedback = viewModel.data?.data?.feedback ?? [];
|
||||
|
||||
if (feedback.isEmpty) {
|
||||
return const Center(
|
||||
child: Text(LikedTendersStrings.noLikedTenders),
|
||||
);
|
||||
}
|
||||
|
||||
final currentPage = viewModel.currentPage;
|
||||
final totalPages = viewModel.data?.data?.meta?.pages ?? 1;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
_likedTendersList(feedback, viewModel),
|
||||
|
||||
SizedBox(height: 10.0.h()),
|
||||
|
||||
_likeTendersListPagesControll(
|
||||
context,
|
||||
totalPages,
|
||||
viewModel,
|
||||
currentPage,
|
||||
),
|
||||
|
||||
SizedBox(height: 30.0.h()),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: Consumer<LikedTendersViewModel>(
|
||||
builder: (context, viewModel, child) {
|
||||
if (viewModel.isLoading &&
|
||||
(viewModel.data?.data?.feedback ?? []).isEmpty) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(
|
||||
color: AppColors.secondary50,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (viewModel.errorMessage != null) {
|
||||
return Center(child: Text(viewModel.errorMessage!));
|
||||
}
|
||||
|
||||
final feedback = viewModel.data?.data?.feedback ?? [];
|
||||
|
||||
if (feedback.isEmpty) {
|
||||
return const Center(
|
||||
child: Text(LikedTendersStrings.noLikedTenders),
|
||||
);
|
||||
}
|
||||
|
||||
final currentPage = viewModel.currentPage;
|
||||
final totalPages =
|
||||
viewModel.data?.data?.meta?.pages ?? 1;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
_likedTendersList(feedback, viewModel),
|
||||
|
||||
SizedBox(height: 10.0.h()),
|
||||
|
||||
_likeTendersListPagesControll(
|
||||
context,
|
||||
totalPages,
|
||||
viewModel,
|
||||
currentPage,
|
||||
),
|
||||
|
||||
SizedBox(height: 30.0.h()),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -29,9 +29,6 @@ class _MobileProfilePageState extends State<MobileProfilePage> {
|
||||
viewModel = context.read<ProfileViewModel>();
|
||||
viewModel.addListener(_profileListener);
|
||||
// Load notification info saved in SharedPreferences
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
viewModel.loadNotificationInfoFromPrefs();
|
||||
});
|
||||
}
|
||||
|
||||
void _profileListener() {
|
||||
@@ -90,18 +87,7 @@ class _MobileProfilePageState extends State<MobileProfilePage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8.0.h()),
|
||||
Align(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: Text(
|
||||
viewModel.notifSummaryText,
|
||||
style: TextStyle(
|
||||
fontSize: 14.0.sp(),
|
||||
fontWeight: FontWeight.w400,
|
||||
color: AppColors.grey60,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
SizedBox(height: 24.0.h()),
|
||||
TitleDescription(
|
||||
title: ProfileStrings.companyName,
|
||||
|
||||
Reference in New Issue
Block a user