diff --git a/lib/core/routes/app_routes.dart b/lib/core/routes/app_routes.dart index 101dda3..3ed20a9 100644 --- a/lib/core/routes/app_routes.dart +++ b/lib/core/routes/app_routes.dart @@ -5,6 +5,7 @@ import 'package:tm_app/views/home/pages/home_screen.dart'; import 'package:tm_app/views/login/pages/login_screen.dart'; import '../../views/forgot_password/pages/forgot_password_screen.dart'; +import '../../views/notification/pages/notification_screen.dart'; import '../../views/profile/pages/profile_screen.dart'; import '../../views/tenders/pages/tenders_screen.dart'; import '../../views/your_tenders/pages/your_tenders_screen.dart'; @@ -19,11 +20,13 @@ final GlobalKey tendersNavigatorKey = GlobalKey(); final GlobalKey profileNavigatorKey = GlobalKey(); +final GlobalKey notificationNavigatorKey = + GlobalKey(); // Main app router final GoRouter appRouter = GoRouter( routes: $appRoutes, - initialLocation: '/login', + initialLocation: '/notification', navigatorKey: rootNavigatorKey, ); @@ -56,6 +59,11 @@ class LoginScreenRoute extends GoRouteData with _$LoginScreenRoute { TypedGoRoute(path: '/profile'), ], ), + TypedStatefulShellBranch( + routes: >[ + TypedGoRoute(path: '/notification'), + ], + ), ], ) class AppShellRouteData extends StatefulShellRouteData { @@ -89,6 +97,12 @@ class ProfileBranch extends StatefulShellBranchData { static final GlobalKey $navigatorKey = profileNavigatorKey; } +class NotificationBranch extends StatefulShellBranchData { + const NotificationBranch(); + static final GlobalKey $navigatorKey = + notificationNavigatorKey; +} + // Route data classes class HomeRouteData extends GoRouteData with _$HomeRouteData { const HomeRouteData(); @@ -138,6 +152,15 @@ class ProfileRouteData extends GoRouteData with _$ProfileRouteData { } } +class NotificationRouteData extends GoRouteData with _$NotificationRouteData { + const NotificationRouteData(); + + @override + Widget build(BuildContext context, GoRouterState state) { + return const NotificationScreen(); + } +} + @TypedGoRoute(path: '/your-tenders') class YourTendersRouteData extends GoRouteData with _$YourTendersRouteData { const YourTendersRouteData(); diff --git a/lib/core/routes/app_routes.g.dart b/lib/core/routes/app_routes.g.dart index aa78fd8..d61ba5a 100644 --- a/lib/core/routes/app_routes.g.dart +++ b/lib/core/routes/app_routes.g.dart @@ -71,6 +71,17 @@ RouteBase get $appShellRouteData => StatefulShellRouteData.$route( ), ], ), + StatefulShellBranchData.$branch( + navigatorKey: NotificationBranch.$navigatorKey, + + routes: [ + GoRouteData.$route( + path: '/notification', + + factory: _$NotificationRouteData._fromState, + ), + ], + ), ], ); @@ -141,6 +152,27 @@ mixin _$ProfileRouteData on GoRouteData { void replace(BuildContext context) => context.replace(location); } +mixin _$NotificationRouteData on GoRouteData { + static NotificationRouteData _fromState(GoRouterState state) => + const NotificationRouteData(); + + @override + String get location => GoRouteData.$location('/notification'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + RouteBase get $yourTendersRouteData => GoRouteData.$route( path: '/your-tenders', diff --git a/lib/gen/assets.gen.dart b/lib/gen/assets.gen.dart index c78f850..bcb1129 100644 --- a/lib/gen/assets.gen.dart +++ b/lib/gen/assets.gen.dart @@ -114,6 +114,9 @@ class $AssetsIconsGen { /// File path: assets/icons/tick.svg String get tick => 'assets/icons/tick.svg'; + /// File path: assets/icons/trailing_icon.svg + String get trailingIcon => 'assets/icons/trailing_icon.svg'; + /// List of all assets List get values => [ se, @@ -149,12 +152,17 @@ class $AssetsIconsGen { thumbLike, tickCircle, tick, + trailingIcon, ]; } class $AssetsPngsGen { const $AssetsPngsGen(); + /// File path: assets/pngs/forgot_password.png + AssetGenImage get forgotPassword => + const AssetGenImage('assets/pngs/forgot_password.png'); + /// File path: assets/pngs/logo.png AssetGenImage get logo => const AssetGenImage('assets/pngs/logo.png'); @@ -163,7 +171,7 @@ class $AssetsPngsGen { const AssetGenImage('assets/pngs/web_login_image.png'); /// List of all assets - List get values => [logo, webLoginImage]; + List get values => [forgotPassword, logo, webLoginImage]; } class $AssetsSvgsGen { diff --git a/lib/views/forgot_password/pages/d_forgot_apssword_page.dart b/lib/views/forgot_password/pages/d_forgot_apssword_page.dart index b70cfdf..f77eda0 100644 --- a/lib/views/forgot_password/pages/d_forgot_apssword_page.dart +++ b/lib/views/forgot_password/pages/d_forgot_apssword_page.dart @@ -59,12 +59,27 @@ class DesktopForgotPasswordPage extends StatelessWidget { ), ), SizedBox(height: 40.0.h()), + LoginTextField( + controller: viewModel.emailController, + focusNode: viewModel.emailFocus, + label: ForgotPasswordStrings.usernameHint, + iconPath: AssetsManager.userIcon, + isPassword: false, + ), + SizedBox(height: 24.0.h()), LoginTextField( controller: viewModel.emailController, focusNode: viewModel.emailFocus, label: ForgotPasswordStrings.emailAddressHint, isPassword: false, ), + SizedBox(height: 24.0.h()), + LoginTextField( + controller: viewModel.emailController, + focusNode: viewModel.emailFocus, + label: ForgotPasswordStrings.companyIdHint, + isPassword: false, + ), SizedBox(height: 70.0.h()), BaseButton( isEnabled: false, diff --git a/lib/views/forgot_password/pages/m_forgot_password_page.dart b/lib/views/forgot_password/pages/m_forgot_password_page.dart index d65c263..1af4d62 100644 --- a/lib/views/forgot_password/pages/m_forgot_password_page.dart +++ b/lib/views/forgot_password/pages/m_forgot_password_page.dart @@ -54,12 +54,27 @@ class MobileForgotPasswordPage extends StatelessWidget { ), ), SizedBox(height: 40.0.h()), + LoginTextField( + controller: viewModel.emailController, + focusNode: viewModel.emailFocus, + label: ForgotPasswordStrings.usernameHint, + iconPath: AssetsManager.userIcon, + isPassword: false, + ), + SizedBox(height: 24.0.h()), LoginTextField( controller: viewModel.emailController, focusNode: viewModel.emailFocus, label: ForgotPasswordStrings.emailAddressHint, isPassword: false, ), + SizedBox(height: 24.0.h()), + LoginTextField( + controller: viewModel.emailController, + focusNode: viewModel.emailFocus, + label: ForgotPasswordStrings.companyIdHint, + isPassword: false, + ), SizedBox(height: 70.0.h()), BaseButton( isEnabled: false, diff --git a/lib/views/forgot_password/pages/t_forgot_password_page.dart b/lib/views/forgot_password/pages/t_forgot_password_page.dart index 858a724..2e8d074 100644 --- a/lib/views/forgot_password/pages/t_forgot_password_page.dart +++ b/lib/views/forgot_password/pages/t_forgot_password_page.dart @@ -53,12 +53,27 @@ class TabletForgotPasswordPage extends StatelessWidget { ), ), SizedBox(height: 40.0.h()), + LoginTextField( + controller: viewModel.emailController, + focusNode: viewModel.emailFocus, + label: ForgotPasswordStrings.usernameHint, + iconPath: AssetsManager.userIcon, + isPassword: false, + ), + SizedBox(height: 24.0.h()), LoginTextField( controller: viewModel.emailController, focusNode: viewModel.emailFocus, label: ForgotPasswordStrings.emailAddressHint, isPassword: false, ), + SizedBox(height: 24.0.h()), + LoginTextField( + controller: viewModel.emailController, + focusNode: viewModel.emailFocus, + label: ForgotPasswordStrings.companyIdHint, + isPassword: false, + ), SizedBox(height: 70.0.h()), BaseButton( isEnabled: false, diff --git a/lib/views/forgot_password/strings/forgot_password_strings.dart b/lib/views/forgot_password/strings/forgot_password_strings.dart index e3c9c76..6ececd5 100644 --- a/lib/views/forgot_password/strings/forgot_password_strings.dart +++ b/lib/views/forgot_password/strings/forgot_password_strings.dart @@ -8,4 +8,6 @@ class ForgotPasswordStrings { static const String emailAddressHint = 'Email Address'; static const String backToSignInButton = 'Back to Sign In'; static const String resetLink = 'Reset Link'; + static const String usernameHint = 'Username'; + static const String companyIdHint = 'Company ID'; } diff --git a/lib/views/notification/pages/d_notification_page.dart b/lib/views/notification/pages/d_notification_page.dart new file mode 100644 index 0000000..1f74b7e --- /dev/null +++ b/lib/views/notification/pages/d_notification_page.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +class DesktopNotificationPage extends StatelessWidget { + const DesktopNotificationPage({super.key}); + + @override + Widget build(BuildContext context) { + return const Scaffold(); + } +} diff --git a/lib/views/notification/pages/m_notification_page.dart b/lib/views/notification/pages/m_notification_page.dart new file mode 100644 index 0000000..22ba539 --- /dev/null +++ b/lib/views/notification/pages/m_notification_page.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; +import 'package:tm_app/core/theme/colors.dart'; +import 'package:tm_app/core/utils/size_config.dart'; +import 'package:tm_app/views/notification/strings/notification_strings.dart'; +import 'package:tm_app/views/shared/tender_app_bar.dart'; + +import '../../shared/main_tab_bar.dart'; +import '../widgets/notification_all_tab.dart'; +import '../widgets/notification_important_tab.dart'; +import '../widgets/notification_unread_tab.dart'; + +class MobileNotificationPage extends StatefulWidget { + const MobileNotificationPage({super.key}); + + @override + State createState() => _MobileNotificationPageState(); +} + +class _MobileNotificationPageState extends State + with SingleTickerProviderStateMixin { + late TabController controller; + + @override + void initState() { + super.initState(); + controller = TabController(length: 3, vsync: this); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.backgroundColor, + appBar: tenderMobileAppBar(title: NotificationStrings.notificationTitle), + body: Column( + children: [ + MainTabBar( + controller: controller, + titles: [ + NotificationStrings.all, + NotificationStrings.unread, + NotificationStrings.important, + ], + ), + SizedBox(height: 12.0.h()), + Padding( + padding: EdgeInsetsDirectional.only(end: 24.0.w()), + child: Align( + alignment: Alignment.centerRight, + child: InkWell( + onTap: () {}, + borderRadius: BorderRadius.circular(99), + child: Container( + width: 132.0.w(), + height: 32.0.h(), + decoration: BoxDecoration( + color: AppColors.primary20, + borderRadius: BorderRadius.circular(99), + ), + child: Center( + child: Text( + NotificationStrings.markAllAsReadButton, + style: TextStyle( + fontSize: 14.0.sp(), + fontWeight: FontWeight.w500, + color: AppColors.mainBlue, + ), + ), + ), + ), + ), + ), + ), + SizedBox(height: 12.0.h()), + Expanded( + child: TabBarView( + controller: controller, + children: [ + NotificationAllTab(), + NotificationUnreadTab(), + NotificationImportantTab(), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/views/notification/pages/notification_screen.dart b/lib/views/notification/pages/notification_screen.dart new file mode 100644 index 0000000..472961d --- /dev/null +++ b/lib/views/notification/pages/notification_screen.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:tm_app/core/utils/size_config.dart'; +import 'package:tm_app/views/notification/pages/d_notification_page.dart'; +import 'package:tm_app/views/notification/pages/m_notification_page.dart'; +import 'package:tm_app/views/notification/pages/t_notification_page.dart'; +import 'package:tm_app/views/shared/responsive_builder.dart'; + +class NotificationScreen extends StatelessWidget { + const NotificationScreen({super.key}); + + @override + Widget build(BuildContext context) { + SizeConfig.init(context); + return ResponsiveBuilder( + mobile: MobileNotificationPage(), + tablet: TabletNotificationPage(), + desktop: DesktopNotificationPage(), + ); + } +} + +class NotificationModel { + final String title; + final String description; + final String time; + final String type; + + NotificationModel({ + required this.title, + required this.description, + required this.time, + required this.type, + }); +} + +final List notifications = [ + NotificationModel( + title: 'New Tender Invitation', + description: 'You’ve been invited to tender #A-778.', + time: '5 Min', + type: 'new', + ), + NotificationModel( + title: 'New Tender Invitation', + description: 'You’ve been invited to tender #N-3762.', + time: '28 Min', + type: 'new', + ), + NotificationModel( + title: 'Tender Submitted', + description: 'You submitted bid for tender #B-554.', + time: '28 Min', + type: 'submit', + ), + NotificationModel( + title: 'New Tender Invitation', + description: 'You’ve been invited to tender #N-3762.', + time: '28 Min', + type: 'new', + ), + NotificationModel( + title: 'Missing Documents', + description: 'Please upload your company registration certificate.', + time: '2 days ago', + type: 'missing', + ), +]; diff --git a/lib/views/notification/pages/t_notification_page.dart b/lib/views/notification/pages/t_notification_page.dart new file mode 100644 index 0000000..6a22aab --- /dev/null +++ b/lib/views/notification/pages/t_notification_page.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +class TabletNotificationPage extends StatelessWidget { + const TabletNotificationPage({super.key}); + + @override + Widget build(BuildContext context) { + return const Scaffold(); + } +} diff --git a/lib/views/notification/strings/notification_strings.dart b/lib/views/notification/strings/notification_strings.dart new file mode 100644 index 0000000..811fe99 --- /dev/null +++ b/lib/views/notification/strings/notification_strings.dart @@ -0,0 +1,9 @@ +class NotificationStrings { + NotificationStrings._(); + + static const String notificationTitle = 'Notification'; + static const String all = 'All'; + static const String unread = 'Unread'; + static const String important = 'Important'; + static const String markAllAsReadButton = 'Mark all as read'; +} diff --git a/lib/views/notification/widgets/notification_all_tab.dart b/lib/views/notification/widgets/notification_all_tab.dart new file mode 100644 index 0000000..6a1b1d0 --- /dev/null +++ b/lib/views/notification/widgets/notification_all_tab.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:tm_app/core/utils/size_config.dart'; + +import '../pages/notification_screen.dart'; +import 'notification_card.dart'; + +class NotificationAllTab extends StatelessWidget { + const NotificationAllTab({super.key}); + + @override + Widget build(BuildContext context) { + return ListView.builder( + itemCount: notifications.length, + itemBuilder: (context, index) { + final notification = notifications[index]; + return Padding( + padding: EdgeInsets.only(bottom: 16.0.h()), + child: NotificationCard(notification: notification), + ); + }, + ); + } +} diff --git a/lib/views/home/widgets/notification_card.dart b/lib/views/notification/widgets/notification_card.dart similarity index 67% rename from lib/views/home/widgets/notification_card.dart rename to lib/views/notification/widgets/notification_card.dart index b2f8861..fc3479e 100644 --- a/lib/views/home/widgets/notification_card.dart +++ b/lib/views/notification/widgets/notification_card.dart @@ -4,15 +4,17 @@ 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 '../strings/home_strings.dart'; +import '../pages/notification_screen.dart'; class NotificationCard extends StatelessWidget { - const NotificationCard({super.key}); + const NotificationCard({required this.notification, super.key}); + final NotificationModel notification; @override Widget build(BuildContext context) { return Container( width: double.infinity, + // height: 108.0.h(), margin: EdgeInsets.symmetric(horizontal: 24.0.w()), padding: EdgeInsets.symmetric(horizontal: 16.0.w(), vertical: 16.0.h()), decoration: BoxDecoration( @@ -21,13 +23,14 @@ class NotificationCard extends StatelessWidget { border: Border.all(color: AppColors.mainBlue.withValues(alpha: 0.15)), ), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ SvgPicture.asset(AssetsManager.notification), SizedBox(width: 8.0.w()), Text( - HomeStrings.welcome, + notification.title, style: TextStyle( fontSize: 16.0.sp(), fontWeight: FontWeight.w500, @@ -37,25 +40,29 @@ class NotificationCard extends StatelessWidget { ], ), SizedBox(height: 8.0.h()), - Text( - 'You’ve been invited Lorem ipsum dolor sit amet consectetur. Volutpat velit tincidunt amet diam. Placerat congue ut Sed facilisis', - maxLines: 3, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 14.0.sp(), - fontWeight: FontWeight.w400, - color: AppColors.grey60, + Padding( + padding: EdgeInsetsDirectional.only(start: 28.0.w()), + child: Text( + notification.description, + maxLines: 1, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.start, + style: TextStyle( + fontSize: 14.0.sp(), + fontWeight: FontWeight.w400, + color: AppColors.grey60, + ), ), ), SizedBox(height: 8.0.h()), Padding( - padding: EdgeInsetsDirectional.only(start: 28.0), + padding: EdgeInsetsDirectional.only(start: 28.0.w()), child: Row( children: [ SvgPicture.asset(AssetsManager.calendar), SizedBox(width: 4.0.w()), Text( - '5 Min', + notification.time, style: TextStyle( fontSize: 14.0.sp(), color: AppColors.grey60, diff --git a/lib/views/notification/widgets/notification_important_tab.dart b/lib/views/notification/widgets/notification_important_tab.dart new file mode 100644 index 0000000..5323cc1 --- /dev/null +++ b/lib/views/notification/widgets/notification_important_tab.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:tm_app/core/utils/size_config.dart'; + +import '../pages/notification_screen.dart'; +import 'notification_card.dart'; + +class NotificationImportantTab extends StatelessWidget { + const NotificationImportantTab({super.key}); + + @override + Widget build(BuildContext context) { + return ListView.builder( + itemCount: notifications.length, + itemBuilder: (context, index) { + final notification = notifications[index]; + return Padding( + padding: EdgeInsets.only(bottom: 16.0.h()), + child: NotificationCard(notification: notification), + ); + }, + ); + } +} diff --git a/lib/views/notification/widgets/notification_unread_tab.dart b/lib/views/notification/widgets/notification_unread_tab.dart new file mode 100644 index 0000000..452fb3a --- /dev/null +++ b/lib/views/notification/widgets/notification_unread_tab.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:tm_app/core/utils/size_config.dart'; + +import '../pages/notification_screen.dart'; +import 'notification_card.dart'; + +class NotificationUnreadTab extends StatelessWidget { + const NotificationUnreadTab({super.key}); + + @override + Widget build(BuildContext context) { + return ListView.builder( + itemCount: notifications.length, + itemBuilder: (context, index) { + final notification = notifications[index]; + return Padding( + padding: EdgeInsets.only(bottom: 16.0.h()), + child: NotificationCard(notification: notification), + ); + }, + ); + } +} diff --git a/lib/views/profile/pages/m_profile_page.dart b/lib/views/profile/pages/m_profile_page.dart index 677fb93..d405506 100644 --- a/lib/views/profile/pages/m_profile_page.dart +++ b/lib/views/profile/pages/m_profile_page.dart @@ -4,12 +4,12 @@ import 'package:provider/provider.dart'; import 'package:tm_app/core/routes/app_routes.dart'; import 'package:tm_app/view_models/profile_view_model.dart'; import 'package:tm_app/views/profile/widgets/theme_toggle.dart'; -import 'package:tm_app/views/shared/tender_app_bar.dart'; import '../../../core/constants/assets.dart'; import '../../../core/constants/common_strings.dart'; import '../../../core/theme/colors.dart'; import '../../../core/utils/size_config.dart'; +import '../../shared/tender_app_bar.dart'; import '../strings/profile_strings.dart'; import '../widgets/title_description.dart'; diff --git a/lib/views/shared/main_tab_bar.dart b/lib/views/shared/main_tab_bar.dart index fb9d36e..0d8c7df 100644 --- a/lib/views/shared/main_tab_bar.dart +++ b/lib/views/shared/main_tab_bar.dart @@ -48,7 +48,11 @@ class _MainTabBarState extends State { fontSize: 16.0.sp(), fontWeight: FontWeight.w600, ), - tabs: [Tab(text: widget.titles[0]), Tab(text: widget.titles[1])], + tabs: [ + Tab(text: widget.titles[0]), + Tab(text: widget.titles[1]), + Tab(text: widget.titles[2]), + ], ), ); }