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:
amirrezaghabeli
2025-10-06 11:59:54 +03:30
parent 17634744f7
commit 42f2e93a8e
28 changed files with 1499 additions and 595 deletions
@@ -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()),
],
);
},
),
),
],
),
),
],
),
),
),
+1 -15
View File
@@ -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,