diff --git a/assets/pngs/logo-big.png b/assets/pngs/logo-big.png new file mode 100644 index 0000000..cb75808 Binary files /dev/null and b/assets/pngs/logo-big.png differ diff --git a/assets/pngs/logo.png b/assets/pngs/logo.png deleted file mode 100644 index 9fbd60d..0000000 Binary files a/assets/pngs/logo.png and /dev/null differ diff --git a/lib/core/constants/assets.dart b/lib/core/constants/assets.dart index 8c821a0..f0fb605 100644 --- a/lib/core/constants/assets.dart +++ b/lib/core/constants/assets.dart @@ -4,6 +4,7 @@ class AssetsManager { //logo static const logoSmall = 'assets/icons/logo_small.svg'; static const logoBig = 'assets/icons/logo_big.svg'; + static const logoBigPng = 'assets/pngs/logo-big.png'; //bottom navigation static const homeActive = 'assets/icons/home_active.svg'; diff --git a/lib/core/routes/app_routes.dart b/lib/core/routes/app_routes.dart index c200dd7..455e70d 100644 --- a/lib/core/routes/app_routes.dart +++ b/lib/core/routes/app_routes.dart @@ -12,6 +12,7 @@ import '../../views/final_completion_of_documents/pages/final_completion_of_docu 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/splash/pages/splash_screen.dart'; import '../../views/tenders/pages/tenders_screen.dart'; import '../../views/your_tenders/pages/your_tenders_screen.dart'; import 'app_shell/app_shell_screen.dart'; @@ -31,7 +32,7 @@ final GlobalKey notificationNavigatorKey = // Main app router final GoRouter appRouter = GoRouter( routes: $appRoutes, - initialLocation: '/login', + initialLocation: '/splash', navigatorKey: rootNavigatorKey, ); @@ -46,6 +47,17 @@ class LoginScreenRoute extends GoRouteData with _$LoginScreenRoute { } } +// splash route - outside the shell +@TypedGoRoute(path: '/splash') +class SplashScreenRoute extends GoRouteData with _$SplashScreenRoute { + const SplashScreenRoute(); + + @override + Widget build(BuildContext context, GoRouterState state) { + return const SplashScreen(); + } +} + // Shell route with bottom navigation using StatefulShellRoute.indexedStack @TypedStatefulShellRoute( branches: >[ diff --git a/lib/core/routes/app_routes.g.dart b/lib/core/routes/app_routes.g.dart index d875f44..14669d1 100644 --- a/lib/core/routes/app_routes.g.dart +++ b/lib/core/routes/app_routes.g.dart @@ -8,6 +8,7 @@ part of 'app_routes.dart'; List get $appRoutes => [ $loginScreenRoute, + $splashScreenRoute, $appShellRouteData, $yourTendersRouteData, $finalCompletionOfDocumentsRouteData, @@ -43,6 +44,33 @@ mixin _$LoginScreenRoute on GoRouteData { void replace(BuildContext context) => context.replace(location); } +RouteBase get $splashScreenRoute => GoRouteData.$route( + path: '/splash', + + factory: _$SplashScreenRoute._fromState, +); + +mixin _$SplashScreenRoute on GoRouteData { + static SplashScreenRoute _fromState(GoRouterState state) => + const SplashScreenRoute(); + + @override + String get location => GoRouteData.$location('/splash'); + + @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 $appShellRouteData => StatefulShellRouteData.$route( parentNavigatorKey: AppShellRouteData.$parentNavigatorKey, factory: $AppShellRouteDataExtension._fromState, diff --git a/lib/data/repositories/auth_repository.dart b/lib/data/repositories/auth_repository.dart index ea3f87f..1325926 100644 --- a/lib/data/repositories/auth_repository.dart +++ b/lib/data/repositories/auth_repository.dart @@ -47,4 +47,8 @@ class AuthRepository { }) async { return _authService.verifyOtp(code: code, email: email); } + + Future> checkIsLoggedIn() async { + return _authService.checkIsLoggedIn(); + } } diff --git a/lib/data/services/auth_service.dart b/lib/data/services/auth_service.dart index 7c762f9..b8c929f 100644 --- a/lib/data/services/auth_service.dart +++ b/lib/data/services/auth_service.dart @@ -113,4 +113,13 @@ class AuthService { return result; } + + Future> checkIsLoggedIn() async { + final prefs = await SharedPreferences.getInstance(); + final token = prefs.getString('bearer'); + if (token == null) { + return const Result.ok(false); + } + return const Result.ok(true); + } } diff --git a/lib/gen/assets.gen.dart b/lib/gen/assets.gen.dart index 06158cb..b5e1b53 100644 --- a/lib/gen/assets.gen.dart +++ b/lib/gen/assets.gen.dart @@ -230,8 +230,8 @@ class $AssetsPngsGen { 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'); + /// File path: assets/pngs/logo-big.png + AssetGenImage get logoBig => const AssetGenImage('assets/pngs/logo-big.png'); /// File path: assets/pngs/web_login_image.png AssetGenImage get webLoginImage => @@ -241,7 +241,7 @@ class $AssetsPngsGen { List get values => [ appLogo, forgotPassword, - logo, + logoBig, webLoginImage, ]; } diff --git a/lib/view_models/auth_view_model.dart b/lib/view_models/auth_view_model.dart index 421e7d1..93f53b0 100644 --- a/lib/view_models/auth_view_model.dart +++ b/lib/view_models/auth_view_model.dart @@ -44,6 +44,7 @@ class AuthViewModel with ChangeNotifier { // Auth state bool _isLoading = false; + bool _isLoggedIn = false; bool _isResetPasswordLoading = false; String? _errorMessage; String? _resetPasswordErrorMessage; @@ -59,6 +60,7 @@ class AuthViewModel with ChangeNotifier { bool _verifyOtpSuccess = false; bool get isLoading => _isLoading; + bool get isLoggedIn => _isLoggedIn; bool get isLoadingForgot => _isLoadingForgot; bool get isLoadingOtp => _isLoadingOtp; bool get isResetPasswordLoading => _isResetPasswordLoading; @@ -282,6 +284,21 @@ class AuthViewModel with ChangeNotifier { } } + Future checkIsLoggedIn() async { + final result = await _authRepository.checkIsLoggedIn(); + + if (result is Ok) { + if (result.value) { + _isLoggedIn = result.value; + } else { + _isLoggedIn = false; + } + } else { + _isLoggedIn = false; + } + notifyListeners(); + } + void clearResetPasswordSuccess() { _resetPasswordSuccess = false; diff --git a/lib/views/splash/pages/d_splash_page.dart b/lib/views/splash/pages/d_splash_page.dart new file mode 100644 index 0000000..531b463 --- /dev/null +++ b/lib/views/splash/pages/d_splash_page.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:tm_app/core/routes/app_routes.dart'; +import 'package:tm_app/view_models/auth_view_model.dart'; + +import '../../../core/constants/assets.dart'; + +class DesktopSplashPage extends StatefulWidget { + const DesktopSplashPage({super.key}); + + @override + State createState() => _DesktopSplashPageState(); +} + +class _DesktopSplashPageState extends State { + late final AuthViewModel viewModel; + @override + void initState() { + super.initState(); + viewModel = context.read(); + Future.delayed(const Duration(seconds: 1), () async { + await viewModel.checkIsLoggedIn(); + }); + viewModel.addListener(_viewModelListener); + } + + @override + void dispose() { + viewModel.removeListener(_viewModelListener); + super.dispose(); + } + + void _viewModelListener() { + if (viewModel.isLoggedIn) { + Router.neglect(context, () => const HomeRouteData().go(context)); + } else { + Router.neglect(context, () => const LoginScreenRoute().go(context)); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Image.asset(AssetsManager.logoBigPng, fit: BoxFit.cover), + ), + ); + } +} diff --git a/lib/views/splash/pages/m_splash_page.dart b/lib/views/splash/pages/m_splash_page.dart new file mode 100644 index 0000000..ea185e1 --- /dev/null +++ b/lib/views/splash/pages/m_splash_page.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:tm_app/core/constants/assets.dart'; +import 'package:tm_app/core/routes/app_routes.dart'; +import 'package:tm_app/view_models/auth_view_model.dart'; + +class MobileSplashPage extends StatefulWidget { + const MobileSplashPage({super.key}); + + @override + State createState() => _MobileSplashPageState(); +} + +class _MobileSplashPageState extends State { + late final AuthViewModel viewModel; + @override + void initState() { + viewModel = context.read(); + Future.delayed(const Duration(seconds: 1), () async { + await viewModel.checkIsLoggedIn(); + }); + viewModel.addListener(_viewModelListener); + super.initState(); + } + + void _viewModelListener() { + if (viewModel.isLoggedIn) { + WidgetsBinding.instance.addPostFrameCallback((_) { + Router.neglect(context, () => const HomeRouteData().go(context)); + }); + } else { + WidgetsBinding.instance.addPostFrameCallback((_) { + Router.neglect(context, () => const LoginScreenRoute().go(context)); + }); + } + } + + @override + void dispose() { + viewModel.removeListener(_viewModelListener); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Image.asset(AssetsManager.logoBigPng, fit: BoxFit.cover), + ), + ); + } +} diff --git a/lib/views/splash/pages/splash_screen.dart b/lib/views/splash/pages/splash_screen.dart new file mode 100644 index 0000000..64ed6c4 --- /dev/null +++ b/lib/views/splash/pages/splash_screen.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:tm_app/core/utils/size_config.dart'; +import 'package:tm_app/views/shared/responsive_builder.dart'; +import 'package:tm_app/views/splash/pages/d_splash_page.dart'; +import 'package:tm_app/views/splash/pages/m_splash_page.dart'; +import 'package:tm_app/views/splash/pages/t_splash_page.dart'; + +class SplashScreen extends StatelessWidget { + const SplashScreen({super.key}); + + @override + Widget build(BuildContext context) { + SizeConfig.init(context); + return const ResponsiveBuilder( + mobile: MobileSplashPage(), + tablet: TabletSplashPage(), + desktop: DesktopSplashPage(), + ); + } +} diff --git a/lib/views/splash/pages/t_splash_page.dart b/lib/views/splash/pages/t_splash_page.dart new file mode 100644 index 0000000..71f85ee --- /dev/null +++ b/lib/views/splash/pages/t_splash_page.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:tm_app/view_models/auth_view_model.dart'; + +import '../../../core/constants/assets.dart'; +import '../../../core/routes/app_routes.dart'; + +class TabletSplashPage extends StatefulWidget { + const TabletSplashPage({super.key}); + + @override + State createState() => _TabletSplashPageState(); +} + +class _TabletSplashPageState extends State { + late final AuthViewModel viewModel; + @override + void initState() { + viewModel = context.read(); + Future.delayed(const Duration(seconds: 1), () async { + await viewModel.checkIsLoggedIn(); + }); + viewModel.addListener(_viewModelListener); + super.initState(); + } + + void _viewModelListener() { + if (viewModel.isLoggedIn) { + Router.neglect(context, () => const HomeRouteData().go(context)); + } else { + Router.neglect(context, () => const LoginScreenRoute().go(context)); + } + } + + @override + void dispose() { + viewModel.removeListener(_viewModelListener); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Image.asset(AssetsManager.logoBigPng, fit: BoxFit.cover), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 386acf0..e8b0d53 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.12.0" + version: "2.13.0" boolean_selector: dependency: transitive description: @@ -269,10 +269,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.3" ffi: dependency: transitive description: @@ -572,10 +572,10 @@ packages: dependency: transitive description: name: leak_tracker - sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "10.0.8" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: @@ -1049,10 +1049,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.3.1" + version: "15.0.0" watcher: dependency: transitive description: