Merge pull request 'forgot_password' (#82) from forgot_password into main

Reviewed-on: https://repo.ravanertebat.com/TM/tm_app/pulls/82
This commit is contained in:
a.ghabeli
2025-09-01 16:57:22 +03:30
27 changed files with 551 additions and 62 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

+6 -1
View File
@@ -85,7 +85,11 @@ List<SingleChildWidget> get viewModels {
create: (context) => HomeViewModel(homeRepository: context.read()), create: (context) => HomeViewModel(homeRepository: context.read()),
), ),
ChangeNotifierProvider( ChangeNotifierProvider(
create: (context) => TendersViewModel(tendersRepository: context.read()), create:
(context) => TendersViewModel(
tendersRepository: context.read(),
homeViewModel: context.read(),
),
), ),
ChangeNotifierProvider( ChangeNotifierProvider(
create: create:
@@ -93,6 +97,7 @@ List<SingleChildWidget> get viewModels {
tenderDetailRepository: context.read(), tenderDetailRepository: context.read(),
tendersRepository: context.read(), tendersRepository: context.read(),
tendersViewModel: context.read(), tendersViewModel: context.read(),
homeViewModel: context.read(),
), ),
), ),
+3
View File
@@ -22,6 +22,9 @@ class AssetsManager {
static const passwordVisibilityOff = static const passwordVisibilityOff =
'assets/svgs/password_visibility_off.svg'; 'assets/svgs/password_visibility_off.svg';
//forgot password page
static const forgotPasswordImage = 'assets/pngs/forgot_password.png';
// tenders page // tenders page
static const tenderLogo = 'assets/icons/tenderLogo.png'; static const tenderLogo = 'assets/icons/tenderLogo.png';
static const like = 'assets/icons/like.svg'; static const like = 'assets/icons/like.svg';
+8
View File
@@ -25,6 +25,14 @@ class AppStrings {
static const passwordLabel = 'Password'; static const passwordLabel = 'Password';
static const loginButton = 'Login'; static const loginButton = 'Login';
//forgot password
static const String forgotPasswordTitle = 'Forgot Password';
static const String forgotPasswordSubtitle =
'Enter your registered email and company ID and submit your request.';
static const String emailAddressHint = 'Email Address';
static const String backToSignInButton = 'Back to Sign In';
static const String resetLink = 'Reset Link';
//Home //Home
static const String home = 'Home'; static const String home = 'Home';
static const String partnership = 'Partnership'; static const String partnership = 'Partnership';
+12
View File
@@ -4,6 +4,7 @@ import 'package:tm_app/views/detail/pages/detail_screen.dart';
import 'package:tm_app/views/home/pages/home_screen.dart'; import 'package:tm_app/views/home/pages/home_screen.dart';
import 'package:tm_app/views/login/pages/login_screen.dart'; import 'package:tm_app/views/login/pages/login_screen.dart';
import '../../views/forgot_password/pages/forgot_password_screen.dart';
import '../../views/profile/pages/profile_screen.dart'; import '../../views/profile/pages/profile_screen.dart';
import '../../views/tenders/pages/tenders_screen.dart'; import '../../views/tenders/pages/tenders_screen.dart';
import '../../views/your_tenders/pages/your_tenders_screen.dart'; import '../../views/your_tenders/pages/your_tenders_screen.dart';
@@ -157,3 +158,14 @@ class TenderDetailRouteData extends GoRouteData with _$TenderDetailRouteData {
return TenderDetailScreen(tenderId: tenderId); return TenderDetailScreen(tenderId: tenderId);
} }
} }
@TypedGoRoute<ForgotPasswordRouteData>(path: '/forgot-password')
class ForgotPasswordRouteData extends GoRouteData
with _$ForgotPasswordRouteData {
const ForgotPasswordRouteData();
@override
Widget build(BuildContext context, GoRouterState state) {
return ForgotPasswordScreen();
}
}
+28
View File
@@ -11,6 +11,7 @@ List<RouteBase> get $appRoutes => [
$appShellRouteData, $appShellRouteData,
$yourTendersRouteData, $yourTendersRouteData,
$tenderDetailRouteData, $tenderDetailRouteData,
$forgotPasswordRouteData,
]; ];
RouteBase get $loginScreenRoute => RouteBase get $loginScreenRoute =>
@@ -198,3 +199,30 @@ mixin _$TenderDetailRouteData on GoRouteData {
@override @override
void replace(BuildContext context) => context.replace(location); void replace(BuildContext context) => context.replace(location);
} }
RouteBase get $forgotPasswordRouteData => GoRouteData.$route(
path: '/forgot-password',
factory: _$ForgotPasswordRouteData._fromState,
);
mixin _$ForgotPasswordRouteData on GoRouteData {
static ForgotPasswordRouteData _fromState(GoRouterState state) =>
const ForgotPasswordRouteData();
@override
String get location => GoRouteData.$location('/forgot-password');
@override
void go(BuildContext context) => context.go(location);
@override
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
@override
void pushReplacement(BuildContext context) =>
context.pushReplacement(location);
@override
void replace(BuildContext context) => context.replace(location);
}
+16
View File
@@ -17,6 +17,9 @@ class $AssetsIconsGen {
/// File path: assets/icons/SE.png /// File path: assets/icons/SE.png
AssetGenImage get se => const AssetGenImage('assets/icons/SE.png'); AssetGenImage get se => const AssetGenImage('assets/icons/SE.png');
/// File path: assets/icons/approve.svg
String get approve => 'assets/icons/approve.svg';
/// File path: assets/icons/arrow-circle-left.svg /// File path: assets/icons/arrow-circle-left.svg
String get arrowCircleLeft => 'assets/icons/arrow-circle-left.svg'; String get arrowCircleLeft => 'assets/icons/arrow-circle-left.svg';
@@ -59,6 +62,12 @@ class $AssetsIconsGen {
/// File path: assets/icons/location.svg /// File path: assets/icons/location.svg
String get location => 'assets/icons/location.svg'; String get location => 'assets/icons/location.svg';
/// File path: assets/icons/logo_big.svg
String get logoBig => 'assets/icons/logo_big.svg';
/// File path: assets/icons/logo_small.svg
String get logoSmall => 'assets/icons/logo_small.svg';
/// File path: assets/icons/menu.svg /// File path: assets/icons/menu.svg
String get menu => 'assets/icons/menu.svg'; String get menu => 'assets/icons/menu.svg';
@@ -96,6 +105,9 @@ class $AssetsIconsGen {
/// File path: assets/icons/thumb.svg /// File path: assets/icons/thumb.svg
String get thumb => 'assets/icons/thumb.svg'; String get thumb => 'assets/icons/thumb.svg';
/// File path: assets/icons/thumb_like.svg
String get thumbLike => 'assets/icons/thumb_like.svg';
/// File path: assets/icons/tick-circle.svg /// File path: assets/icons/tick-circle.svg
String get tickCircle => 'assets/icons/tick-circle.svg'; String get tickCircle => 'assets/icons/tick-circle.svg';
@@ -105,6 +117,7 @@ class $AssetsIconsGen {
/// List of all assets /// List of all assets
List<dynamic> get values => [ List<dynamic> get values => [
se, se,
approve,
arrowCircleLeft, arrowCircleLeft,
arrowCircleRight, arrowCircleRight,
arrowLeftSmall, arrowLeftSmall,
@@ -119,6 +132,8 @@ class $AssetsIconsGen {
homeActive, homeActive,
like, like,
location, location,
logoBig,
logoSmall,
menu, menu,
moon, moon,
notification, notification,
@@ -131,6 +146,7 @@ class $AssetsIconsGen {
tenderLogoPng, tenderLogoPng,
tenderLogoSvg, tenderLogoSvg,
thumb, thumb,
thumbLike,
tickCircle, tickCircle,
tick, tick,
]; ];
+2
View File
@@ -19,8 +19,10 @@ class AuthViewModel with ChangeNotifier {
final TextEditingController usernameController = TextEditingController(); final TextEditingController usernameController = TextEditingController();
final TextEditingController passwordController = TextEditingController(); final TextEditingController passwordController = TextEditingController();
final TextEditingController emailController = TextEditingController();
final FocusNode usernameFocus = FocusNode(); final FocusNode usernameFocus = FocusNode();
final FocusNode passwordFocus = FocusNode(); final FocusNode passwordFocus = FocusNode();
final FocusNode emailFocus = FocusNode();
bool _obscurePassword = true; bool _obscurePassword = true;
bool get obscurePassword => _obscurePassword; bool get obscurePassword => _obscurePassword;
@@ -8,20 +8,24 @@ import 'package:tm_app/data/services/model/tender_detail_response/tender_detail_
import '../data/services/model/tender_approvals_by_id_response/tender_approvals_by_id_response.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_data/tender_approvals_data.dart';
import '../data/services/model/tender_approvals_toggle_response/tender_approvals_toggle_response.dart'; import '../data/services/model/tender_approvals_toggle_response/tender_approvals_toggle_response.dart';
import 'home_view_model.dart';
import 'tenders_view_model.dart'; import 'tenders_view_model.dart';
class TenderDetailViewModel with ChangeNotifier { class TenderDetailViewModel with ChangeNotifier {
final TenderDetailRepository _tenderDetailRepository; final TenderDetailRepository _tenderDetailRepository;
final TendersRepository _tendersRepository; final TendersRepository _tendersRepository;
final TendersViewModel _tendersViewModel; final TendersViewModel _tendersViewModel;
final HomeViewModel _homeViewModel;
TenderDetailViewModel({ TenderDetailViewModel({
required TenderDetailRepository tenderDetailRepository, required TenderDetailRepository tenderDetailRepository,
required TendersRepository tendersRepository, required TendersRepository tendersRepository,
required TendersViewModel tendersViewModel, required TendersViewModel tendersViewModel,
required HomeViewModel homeViewModel,
}) : _tenderDetailRepository = tenderDetailRepository, }) : _tenderDetailRepository = tenderDetailRepository,
_tendersRepository = tendersRepository, _tendersRepository = tendersRepository,
_tendersViewModel = tendersViewModel; _tendersViewModel = tendersViewModel,
_homeViewModel = homeViewModel;
bool _isLoading = false; bool _isLoading = false;
bool _isSubmitApprovalLoading = false; bool _isSubmitApprovalLoading = false;
@@ -122,6 +126,10 @@ class TenderDetailViewModel with ChangeNotifier {
_tendersViewModel.getTenderApprovals(tenderId); _tendersViewModel.getTenderApprovals(tenderId);
} }
void updateHome() async {
_homeViewModel.init();
}
Future<void> getTenderApprovals(String tenderId) async { Future<void> getTenderApprovals(String tenderId) async {
_errorMessage = null; _errorMessage = null;
notifyListeners(); notifyListeners();
+11 -2
View File
@@ -11,12 +11,17 @@ import 'package:tm_app/data/services/model/tenders/tenders_response/tenders_resp
import '../core/utils/result.dart'; import '../core/utils/result.dart';
import '../data/services/model/tender_approvals_by_id_response/tender_approvals_by_id_response.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_data/tender_approvals_data.dart';
import 'home_view_model.dart';
class TendersViewModel with ChangeNotifier { class TendersViewModel with ChangeNotifier {
final TendersRepository _tendersRepository; final TendersRepository _tendersRepository;
final HomeViewModel _homeViewModel;
TendersViewModel({required TendersRepository tendersRepository}) TendersViewModel({
: _tendersRepository = tendersRepository { required TendersRepository tendersRepository,
required HomeViewModel homeViewModel,
}) : _tendersRepository = tendersRepository,
_homeViewModel = homeViewModel {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
getTenders(); getTenders();
}); });
@@ -457,4 +462,8 @@ class TendersViewModel with ChangeNotifier {
_tenderApprovalErrorMessage = null; _tenderApprovalErrorMessage = null;
notifyListeners(); notifyListeners();
} }
void updateHome() {
_homeViewModel.init();
}
} }
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/core/utils/size_config.dart';
@@ -9,7 +8,7 @@ import 'package:tm_app/views/detail/widgets/tender_detail_card.dart';
import 'package:tm_app/views/detail/widgets/tender_detail_header.dart'; import 'package:tm_app/views/detail/widgets/tender_detail_header.dart';
import '../../../core/constants/strings.dart'; import '../../../core/constants/strings.dart';
import '../../../view_models/home_view_model.dart'; import '../../../core/routes/app_routes.dart';
import '../../shared/desktop_navigation_widget.dart'; import '../../shared/desktop_navigation_widget.dart';
class TenderDetailDesktopPage extends StatefulWidget { class TenderDetailDesktopPage extends StatefulWidget {
@@ -80,9 +79,7 @@ class _TenderDetailDesktopPageState extends State<TenderDetailDesktopPage> {
return PopScope( return PopScope(
onPopInvokedWithResult: (didPop, result) { onPopInvokedWithResult: (didPop, result) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<TenderDetailViewModel>().updateTenderApprovals( viewModel.updateTenderApprovals(widget.tenderId);
widget.tenderId,
);
}); });
}, },
child: Scaffold( child: Scaffold(
@@ -112,14 +109,20 @@ class _TenderDetailDesktopPageState extends State<TenderDetailDesktopPage> {
// Navigate to different screens based on index // Navigate to different screens based on index
switch (index) { switch (index) {
case 0: case 0:
Router.neglect(context, () => context.go('/home')); Router.neglect(
context.read<HomeViewModel>().init(); context,
() => HomeRouteData().go(context),
);
viewModel.updateHome();
break; break;
case 1: case 1:
break; break;
case 2: case 2:
// Navigate to profile // Navigate to profile
Router.neglect(context, () => context.go('/profile')); Router.neglect(
context,
() => ProfileRouteData().go(context),
);
break; break;
} }
}, },
@@ -6,7 +6,6 @@ import 'package:provider/provider.dart';
import 'package:tm_app/core/constants/assets.dart'; import 'package:tm_app/core/constants/assets.dart';
import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/core/utils/size_config.dart';
import 'package:tm_app/view_models/home_view_model.dart';
import 'package:tm_app/view_models/tender_detail_view_model.dart'; import 'package:tm_app/view_models/tender_detail_view_model.dart';
import 'package:tm_app/views/detail/widgets/tender_detail_action.dart'; import 'package:tm_app/views/detail/widgets/tender_detail_action.dart';
import 'package:tm_app/views/detail/widgets/tender_detail_card.dart'; import 'package:tm_app/views/detail/widgets/tender_detail_card.dart';
@@ -14,6 +13,7 @@ import 'package:tm_app/views/detail/widgets/tender_detail_header.dart';
import 'package:tm_app/views/shared/tablet_navigation_widget.dart'; import 'package:tm_app/views/shared/tablet_navigation_widget.dart';
import '../../../core/constants/strings.dart'; import '../../../core/constants/strings.dart';
import '../../../core/routes/app_routes.dart';
class TenderDetailTabletPage extends StatefulWidget { class TenderDetailTabletPage extends StatefulWidget {
final String tenderId; final String tenderId;
@@ -84,9 +84,7 @@ class _TenderDetailTabletPageState extends State<TenderDetailTabletPage> {
onPopInvokedWithResult: (didPop, result) { onPopInvokedWithResult: (didPop, result) {
if (kIsWeb) { if (kIsWeb) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<TenderDetailViewModel>().updateTenderApprovals( viewModel.updateTenderApprovals(widget.tenderId);
widget.tenderId,
);
}); });
} }
}, },
@@ -124,15 +122,15 @@ class _TenderDetailTabletPageState extends State<TenderDetailTabletPage> {
// Handle navigation to other tabs // Handle navigation to other tabs
switch (index) { switch (index) {
case 0: case 0:
Router.neglect(context, () => context.go('/home')); Router.neglect(context, () => HomeRouteData().go(context));
context.read<HomeViewModel>().init(); viewModel.updateHome();
break; break;
case 1: case 1:
break; break;
case 2: case 2:
// Navigate to profile // Navigate to profile
context.pop(); context.pop();
Router.neglect(context, () => context.go('/profile')); Router.neglect(context, () => ProfileRouteData().go(context));
break; break;
} }
}, },
@@ -0,0 +1,116 @@
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/utils/size_config.dart';
import '../../../core/constants/assets.dart';
import '../../../core/constants/strings.dart';
import '../../../core/theme/colors.dart';
import '../../../view_models/auth_view_model.dart';
import '../../login/widgets/login_textfield.dart';
import '../../shared/base_button.dart';
class DesktopForgotPasswordPage extends StatelessWidget {
const DesktopForgotPasswordPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.backgroundColor,
body: Center(
child: Consumer<AuthViewModel>(
builder: (context, viewModel, _) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 120),
child: SizedBox(
width: 364,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 22.0.w()),
child: SvgPicture.asset(
AssetsManager.logoBig,
width: double.infinity,
height: 50.0.h(),
),
),
SizedBox(height: 72.0.h()),
Text(
AppStrings.forgotPasswordTitle,
style: TextStyle(
fontSize: 28.0.sp(),
fontWeight: FontWeight.bold,
color: AppColors.grey80,
),
),
SizedBox(height: 16.0.h()),
Text(
AppStrings.forgotPasswordSubtitle,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey60,
),
),
SizedBox(height: 40.0.h()),
LoginTextField(
controller: viewModel.emailController,
focusNode: viewModel.emailFocus,
label: AppStrings.emailAddressHint,
isPassword: false,
),
SizedBox(height: 70.0.h()),
BaseButton(
isEnabled: false,
text: AppStrings.resetLink,
onPressed: () async {
if (viewModel.isLoginEnabled) {
await viewModel.login();
}
},
),
SizedBox(height: 12),
TextButton(
onPressed: () {
context.pop();
},
child: Text(
AppStrings.backToSignInButton,
style: TextStyle(
fontSize: 14.0.sp(),
fontWeight: FontWeight.w500,
color: AppColors.mainBlue,
),
),
),
],
),
),
),
Padding(
padding: EdgeInsets.symmetric(vertical: 20.0.h()),
child: ClipRRect(
borderRadius: BorderRadius.circular(32.0),
child: Image.asset(
AssetsManager.forgotPasswordImage,
width: 796.0,
height: 944.0,
fit: BoxFit.fitWidth,
),
),
),
],
);
},
),
),
);
}
}
@@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
import '../../shared/responsive_builder.dart';
import 'd_forgot_apssword_page.dart';
import 'm_forgot_password_page.dart';
import 't_forgot_password_page.dart';
class ForgotPasswordScreen extends StatelessWidget {
const ForgotPasswordScreen({super.key});
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(
mobile: MobileForgotPasswordPage(),
tablet: TabletForgotPasswordPage(),
desktop: DesktopForgotPasswordPage(),
);
}
}
@@ -0,0 +1,94 @@
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/utils/size_config.dart';
import '../../../core/constants/assets.dart';
import '../../../core/constants/strings.dart';
import '../../../core/theme/colors.dart';
import '../../../view_models/auth_view_model.dart';
import '../../login/widgets/login_textfield.dart';
import '../../shared/base_button.dart';
class MobileForgotPasswordPage extends StatelessWidget {
const MobileForgotPasswordPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.backgroundColor,
body: Center(
child: SingleChildScrollView(
padding: EdgeInsets.symmetric(horizontal: 24.0.w()),
child: Consumer<AuthViewModel>(
builder: (context, viewModel, _) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 22.0.w()),
child: SvgPicture.asset(
AssetsManager.logoBig,
width: double.infinity,
height: 50.0.h(),
),
),
SizedBox(height: 72.0.h()),
Text(
AppStrings.forgotPasswordTitle,
style: TextStyle(
fontSize: 28.0.sp(),
fontWeight: FontWeight.bold,
color: AppColors.grey80,
),
),
SizedBox(height: 16.0.h()),
Text(
AppStrings.forgotPasswordSubtitle,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey60,
),
),
SizedBox(height: 40.0.h()),
LoginTextField(
controller: viewModel.emailController,
focusNode: viewModel.emailFocus,
label: AppStrings.emailAddressHint,
isPassword: false,
),
SizedBox(height: 70.0.h()),
BaseButton(
isEnabled: false,
text: AppStrings.resetLink,
onPressed: () async {
if (viewModel.isLoginEnabled) {
await viewModel.login();
}
},
),
TextButton(
onPressed: () {
context.pop();
},
child: Text(
AppStrings.backToSignInButton,
style: TextStyle(
fontSize: 14.0.sp(),
fontWeight: FontWeight.w500,
color: AppColors.mainBlue,
),
),
),
],
);
},
),
),
),
);
}
}
@@ -0,0 +1,94 @@
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/utils/size_config.dart';
import '../../../core/constants/assets.dart';
import '../../../core/constants/strings.dart';
import '../../../core/theme/colors.dart';
import '../../../view_models/auth_view_model.dart';
import '../../login/widgets/login_textfield.dart';
import '../../shared/base_button.dart';
class TabletForgotPasswordPage extends StatelessWidget {
const TabletForgotPasswordPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Consumer<AuthViewModel>(
builder: (context, viewModel, _) {
return Center(
child: SizedBox(
width: 364,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 22.0.w()),
child: SvgPicture.asset(
AssetsManager.logoBig,
width: double.infinity,
height: 50.0.h(),
),
),
SizedBox(height: 72.0.h()),
Text(
AppStrings.forgotPasswordTitle,
style: TextStyle(
fontSize: 28.0.sp(),
fontWeight: FontWeight.bold,
color: AppColors.grey80,
),
),
SizedBox(height: 16.0.h()),
Text(
AppStrings.forgotPasswordSubtitle,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14.0.sp(),
fontWeight: FontWeight.w400,
color: AppColors.grey60,
),
),
SizedBox(height: 40.0.h()),
LoginTextField(
controller: viewModel.emailController,
focusNode: viewModel.emailFocus,
label: AppStrings.emailAddressHint,
isPassword: false,
),
SizedBox(height: 70.0.h()),
BaseButton(
isEnabled: false,
text: AppStrings.resetLink,
onPressed: () async {
if (viewModel.isLoginEnabled) {
await viewModel.login();
}
},
),
SizedBox(height: 12),
TextButton(
onPressed: () {
context.pop();
},
child: Text(
AppStrings.backToSignInButton,
style: TextStyle(
fontSize: 14.0.sp(),
fontWeight: FontWeight.w500,
color: AppColors.mainBlue,
),
),
),
],
),
),
);
},
),
);
}
}
+8 -3
View File
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tm_app/core/constants/assets.dart'; import 'package:tm_app/core/constants/assets.dart';
import 'package:tm_app/core/constants/strings.dart'; import 'package:tm_app/core/constants/strings.dart';
@@ -44,11 +43,17 @@ class DesktopHomePage extends StatelessWidget {
break; break;
case 1: case 1:
// Navigate to tenders // Navigate to tenders
Router.neglect(context, () => context.go('/tenders')); Router.neglect(
context,
() => TendersRouteData().go(context),
);
break; break;
case 2: case 2:
// Navigate to profile // Navigate to profile
Router.neglect(context, () => context.go('/profile')); Router.neglect(
context,
() => ProfileRouteData().go(context),
);
break; break;
} }
}, },
+2 -2
View File
@@ -59,12 +59,12 @@ class TabletHomePage extends StatelessWidget {
case 1: case 1:
// Navigate to tenders // Navigate to tenders
context.pop(); context.pop();
Router.neglect(context, () => context.go('/tenders')); Router.neglect(context, () => TendersRouteData().go(context));
break; break;
case 2: case 2:
// Navigate to profile // Navigate to profile
context.pop(); context.pop();
Router.neglect(context, () => context.go('/profile')); Router.neglect(context, () => ProfileRouteData().go(context));
break; break;
} }
}, },
+16 -2
View File
@@ -7,6 +7,7 @@ import 'package:tm_app/core/theme/colors.dart';
import '../../../core/constants/assets.dart'; import '../../../core/constants/assets.dart';
import '../../../core/constants/strings.dart'; import '../../../core/constants/strings.dart';
import '../../../core/routes/app_routes.dart';
import '../../../core/utils/size_config.dart'; import '../../../core/utils/size_config.dart';
import '../../../view_models/auth_view_model.dart'; import '../../../view_models/auth_view_model.dart';
import '../widgets/widgets.dart'; import '../widgets/widgets.dart';
@@ -46,7 +47,6 @@ class _LoginDesktopPageState extends State<LoginDesktopPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
SizeConfig.init(context);
return Scaffold( return Scaffold(
backgroundColor: AppColors.backgroundColor, backgroundColor: AppColors.backgroundColor,
body: Center( body: Center(
@@ -55,7 +55,7 @@ class _LoginDesktopPageState extends State<LoginDesktopPage> {
kIsWeb ? MainAxisAlignment.spaceAround : MainAxisAlignment.center, kIsWeb ? MainAxisAlignment.spaceAround : MainAxisAlignment.center,
children: [ children: [
SizedBox( SizedBox(
width: 526, width: 536,
child: Consumer<AuthViewModel>( child: Consumer<AuthViewModel>(
builder: (context, viewModel, _) { builder: (context, viewModel, _) {
return Column( return Column(
@@ -108,6 +108,20 @@ class _LoginDesktopPageState extends State<LoginDesktopPage> {
} }
}, },
), ),
SizedBox(height: 32.0),
TextButton(
onPressed: () {
ForgotPasswordRouteData().push(context);
},
child: Text(
AppStrings.forgotPassword,
style: TextStyle(
fontSize: 14.0.sp(),
fontWeight: FontWeight.w500,
color: AppColors.mainBlue,
),
),
),
], ],
); );
}, },
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tm_app/core/routes/app_routes.dart';
import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/core/theme/colors.dart';
import '../../../core/constants/assets.dart'; import '../../../core/constants/assets.dart';
@@ -103,6 +104,20 @@ class _LoginMobilePageState extends State<LoginMobilePage> {
} }
}, },
), ),
SizedBox(height: 32.0.h()),
TextButton(
onPressed: () {
ForgotPasswordRouteData().push(context);
},
child: Text(
AppStrings.forgotPassword,
style: TextStyle(
fontSize: 14.0.sp(),
fontWeight: FontWeight.w500,
color: AppColors.mainBlue,
),
),
),
], ],
); );
}, },
+15 -1
View File
@@ -5,6 +5,7 @@ import 'package:tm_app/core/theme/colors.dart';
import '../../../core/constants/assets.dart'; import '../../../core/constants/assets.dart';
import '../../../core/constants/strings.dart'; import '../../../core/constants/strings.dart';
import '../../../core/routes/app_routes.dart';
import '../../../core/utils/size_config.dart'; import '../../../core/utils/size_config.dart';
import '../../../view_models/auth_view_model.dart'; import '../../../view_models/auth_view_model.dart';
import '../widgets/widgets.dart'; import '../widgets/widgets.dart';
@@ -45,7 +46,6 @@ class _LoginTabletPageState extends State<LoginTabletPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
SizeConfig.init(context);
return Scaffold( return Scaffold(
backgroundColor: AppColors.backgroundColor, backgroundColor: AppColors.backgroundColor,
body: Center( body: Center(
@@ -101,6 +101,20 @@ class _LoginTabletPageState extends State<LoginTabletPage> {
} }
: null, : null,
), ),
SizedBox(height: 32.0),
TextButton(
onPressed: () {
ForgotPasswordRouteData().push(context);
},
child: Text(
AppStrings.forgotPassword,
style: TextStyle(
fontSize: 14.0.sp(),
fontWeight: FontWeight.w500,
color: AppColors.mainBlue,
),
),
),
], ],
); );
}, },
+19 -14
View File
@@ -9,7 +9,7 @@ class LoginTextField extends StatelessWidget {
final TextEditingController controller; final TextEditingController controller;
final FocusNode focusNode; final FocusNode focusNode;
final String label; final String label;
final String iconPath; final String? iconPath;
final bool isPassword; final bool isPassword;
final bool obscureText; final bool obscureText;
final double? borderRedus; final double? borderRedus;
@@ -21,7 +21,7 @@ class LoginTextField extends StatelessWidget {
required this.controller, required this.controller,
required this.focusNode, required this.focusNode,
required this.label, required this.label,
required this.iconPath, this.iconPath,
super.key, super.key,
this.isPassword = false, this.isPassword = false,
this.obscureText = false, this.obscureText = false,
@@ -45,18 +45,23 @@ class LoginTextField extends StatelessWidget {
horizontal: 12.0.w(), horizontal: 12.0.w(),
), ),
labelText: label, labelText: label,
prefixIcon: Padding( prefixIcon:
padding: EdgeInsets.all(prefixIconPadding ?? 12.0.w()), iconPath != null
child: SvgPicture.asset( ? Padding(
iconPath, padding: EdgeInsets.all(prefixIconPadding ?? 12.0.w()),
width: 24.0.w(), child: SvgPicture.asset(
height: 24.0.h(), iconPath!,
colorFilter: ColorFilter.mode( width: 24.0.w(),
focusNode.hasFocus ? AppColors.grey80 : AppColors.grey60, height: 24.0.h(),
BlendMode.srcIn, colorFilter: ColorFilter.mode(
), focusNode.hasFocus
), ? AppColors.grey80
), : AppColors.grey60,
BlendMode.srcIn,
),
),
)
: null,
suffixIcon: suffixIcon:
isPassword isPassword
? IconButton( ? IconButton(
+10 -4
View File
@@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tm_app/core/routes/app_routes.dart'; import 'package:tm_app/core/routes/app_routes.dart';
import 'package:tm_app/view_models/profile_view_model.dart'; import 'package:tm_app/view_models/profile_view_model.dart';
@@ -39,7 +38,7 @@ class _DesktopProfilePageState extends State<DesktopProfilePage> {
); );
} }
if (viewModel.loggedOut) { if (viewModel.loggedOut) {
LoginScreenRoute().go(context); Router.neglect(context, () => LoginScreenRoute().go(context));
} }
} }
@@ -77,11 +76,17 @@ class _DesktopProfilePageState extends State<DesktopProfilePage> {
switch (index) { switch (index) {
case 0: case 0:
// Navigate to home // Navigate to home
Router.neglect(context, () => context.go('/home')); Router.neglect(
context,
() => HomeRouteData().go(context),
);
break; break;
case 1: case 1:
// Navigate to profile // Navigate to profile
Router.neglect(context, () => context.go('/tenders')); Router.neglect(
context,
() => TendersRouteData().go(context),
);
break; break;
case 2: case 2:
// Already on profile // Already on profile
@@ -163,6 +168,7 @@ class _DesktopProfilePageState extends State<DesktopProfilePage> {
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: InkWell( child: InkWell(
onTap: () => viewModel.logout(), onTap: () => viewModel.logout(),
borderRadius: BorderRadius.circular(100),
child: Container( child: Container(
width: 156, width: 156,
height: 56.0.h(), height: 56.0.h(),
+2 -1
View File
@@ -38,7 +38,7 @@ class _MobileProfilePageState extends State<MobileProfilePage> {
); );
} }
if (viewModel.loggedOut) { if (viewModel.loggedOut) {
LoginScreenRoute().go(context); Router.neglect(context, () => LoginScreenRoute().go(context));
} }
} }
@@ -143,6 +143,7 @@ class _MobileProfilePageState extends State<MobileProfilePage> {
Spacer(), Spacer(),
InkWell( InkWell(
onTap: () => viewModel.logout(), onTap: () => viewModel.logout(),
borderRadius: BorderRadius.circular(100),
child: Container( child: Container(
width: double.infinity, width: double.infinity,
height: 56.0.h(), height: 56.0.h(),
+8 -5
View File
@@ -5,7 +5,6 @@ import 'package:provider/provider.dart';
import 'package:tm_app/core/constants/assets.dart'; import 'package:tm_app/core/constants/assets.dart';
import 'package:tm_app/core/constants/strings.dart'; import 'package:tm_app/core/constants/strings.dart';
import 'package:tm_app/core/routes/app_routes.dart'; import 'package:tm_app/core/routes/app_routes.dart';
import 'package:tm_app/view_models/home_view_model.dart';
import 'package:tm_app/view_models/profile_view_model.dart'; import 'package:tm_app/view_models/profile_view_model.dart';
import '../../../core/theme/colors.dart'; import '../../../core/theme/colors.dart';
@@ -40,7 +39,7 @@ class _TabletProfilePageState extends State<TabletProfilePage> {
); );
} }
if (viewModel.loggedOut) { if (viewModel.loggedOut) {
LoginScreenRoute().go(context); Router.neglect(context, () => LoginScreenRoute().go(context));
} }
} }
@@ -103,12 +102,15 @@ class _TabletProfilePageState extends State<TabletProfilePage> {
switch (index) { switch (index) {
case 0: case 0:
context.pop(); context.pop();
Router.neglect(context, () => context.go('/home')); Router.neglect(context, () => HomeRouteData().go(context));
context.read<HomeViewModel>().init();
break; break;
case 1: case 1:
context.pop(); context.pop();
Router.neglect(context, () => context.go('/tenders')); Router.neglect(
context,
() => TendersRouteData().go(context),
);
break; break;
} }
}, },
@@ -190,6 +192,7 @@ class _TabletProfilePageState extends State<TabletProfilePage> {
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: InkWell( child: InkWell(
onTap: () => viewModel.logout(), onTap: () => viewModel.logout(),
borderRadius: BorderRadius.circular(100),
child: Container( child: Container(
width: 156, width: 156,
height: 56.0.h(), height: 56.0.h(),
+4 -6
View File
@@ -1,14 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/core/utils/size_config.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/view_models/tenders_view_model.dart';
import 'package:tm_app/views/shared/desktop_navigation_widget.dart'; import 'package:tm_app/views/shared/desktop_navigation_widget.dart';
import 'package:tm_app/views/tenders/widgets/main_tenders_slider.dart'; import 'package:tm_app/views/tenders/widgets/main_tenders_slider.dart';
import '../../../core/constants/strings.dart'; import '../../../core/constants/strings.dart';
import '../../../core/routes/app_routes.dart';
class DesktopTendersPage extends StatefulWidget { class DesktopTendersPage extends StatefulWidget {
const DesktopTendersPage({super.key}); const DesktopTendersPage({super.key});
@@ -64,16 +63,15 @@ class _DesktopTendersPageState extends State<DesktopTendersPage> {
switch (index) { switch (index) {
case 0: case 0:
// Navigate to home // Navigate to home
Router.neglect(context, () => context.go('/home')); Router.neglect(context, () => HomeRouteData().go(context));
context.read<HomeViewModel>().init(); viewModel.updateHome();
break; break;
case 1: case 1:
// Already on tenders // Already on tenders
break; break;
case 2: case 2:
// Navigate to profile // Navigate to profile
// context.go('/profile'); Router.neglect(context, () => ProfileRouteData().go(context));
Router.neglect(context, () => context.go('/profile'));
break; break;
} }
}, },
+18 -5
View File
@@ -4,17 +4,30 @@ import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/core/theme/colors.dart';
import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/core/utils/size_config.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/view_models/tenders_view_model.dart';
import 'package:tm_app/views/shared/tablet_navigation_widget.dart'; import 'package:tm_app/views/shared/tablet_navigation_widget.dart';
import 'package:tm_app/views/tenders/widgets/main_tenders_slider.dart'; import 'package:tm_app/views/tenders/widgets/main_tenders_slider.dart';
import '../../../core/constants/assets.dart'; import '../../../core/constants/assets.dart';
import '../../../core/constants/strings.dart'; import '../../../core/constants/strings.dart';
import '../../../core/routes/app_routes.dart';
class TabletTendersPage extends StatelessWidget { class TabletTendersPage extends StatefulWidget {
const TabletTendersPage({super.key}); const TabletTendersPage({super.key});
@override
State<TabletTendersPage> createState() => _TabletTendersPageState();
}
class _TabletTendersPageState extends State<TabletTendersPage> {
late final TendersViewModel viewModel;
@override
void initState() {
super.initState();
viewModel = context.read<TendersViewModel>();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final GlobalKey<ScaffoldState> key = GlobalKey(); final GlobalKey<ScaffoldState> key = GlobalKey();
@@ -52,12 +65,12 @@ class TabletTendersPage extends StatelessWidget {
switch (index) { switch (index) {
case 0: case 0:
context.pop(); context.pop();
Router.neglect(context, () => context.go('/home')); Router.neglect(context, () => HomeRouteData().go(context));
context.read<HomeViewModel>().init(); viewModel.updateHome();
break; break;
case 2: case 2:
context.pop(); context.pop();
Router.neglect(context, () => context.go('/profile')); Router.neglect(context, () => ProfileRouteData().go(context));
break; break;
} }