diff --git a/app/lib/main/app.dart b/app/lib/main/app.dart index 7ed667d..018ad8b 100644 --- a/app/lib/main/app.dart +++ b/app/lib/main/app.dart @@ -1,24 +1,63 @@ -import 'package:common/core/resource.dart'; import 'package:flutter/material.dart'; import 'package:domain/bloc/app/app_cubit.dart'; import 'package:domain/bloc/app/app_state.dart'; import 'package:domain/bloc/auth/auth_cubit.dart'; -import 'package:domain/bloc/auth/auth_state.dart'; import 'package:app/presentation/navigation/routers.dart'; import 'package:app/presentation/resources/locale/generated/l10n.dart'; import 'package:app/presentation/themes/app_themes.dart'; import 'package:app/presentation/utils/lang_extensions.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:app_links/app_links.dart'; import 'package:go_router/go_router.dart'; - import 'init.dart'; -class App extends StatelessWidget { - GoRouter get _goRouter => Routers.authRouter; - +class App extends StatefulWidget { const App({super.key}); + @override + State createState() => _AppState(); +} + +class _AppState extends State { + late final GoRouter _router; + bool _isRouterReady = false; + + @override + void initState() { + super.initState(); + _initRouter(); + } + + Future _initRouter() async { + String? initialLocation; + if (kIsWeb) { + final path = Uri.base.path; + if (path.isNotEmpty && path != '/') { + initialLocation = path; + } + } else { + final appLinks = AppLinks(); + final initialUri = await appLinks.getInitialLink(); + if (initialUri != null) { + initialLocation = initialUri.path; + } + } + + debugPrint('App entry point: $initialLocation'); + + if (mounted) { + setState(() { + _router = Routes.init( + context, + initialLocation: initialLocation, + ); + _isRouterReady = true; + }); + } + } + @override Widget build(BuildContext context) { return MultiBlocProvider( @@ -26,38 +65,35 @@ class App extends StatelessWidget { BlocProvider(create: (_) => getIt()), BlocProvider(create: (_) => getIt()), ], - child: BlocBuilder( - builder: (context, state) { - return MaterialApp.router( - theme: AppThemes.getAppTheme(state.themeType).data, - locale: LangExtensions.langLocale[state.appLang], - supportedLocales: LangExtensions.supportedLang, - localizationsDelegates: const [ - S.delegate, - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - builder: (context, child) { - return BlocListener( - listener: (_, state) { - if (state is RSuccess) { - switch (state.data) { - case AuthStateAuthenticated _: - _goRouter.go('/home'); - case AuthStateUnauthenticated _: - _goRouter.go('/login'); - case _: - } - } - }, - child: child, - ); - }, - routerConfig: _goRouter, - ); - }, - ), + child: !_isRouterReady + ? const Material( + child: Center( + child: CircularProgressIndicator(), + ), + ) + : BlocBuilder( + builder: (context, state) { + return MaterialApp.router( + theme: AppThemes.getAppTheme(state.themeType).data, + locale: LangExtensions.langLocale[state.appLang], + supportedLocales: LangExtensions.supportedLang, + localizationsDelegates: const [ + S.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + builder: (context, child) => + child ?? + const Material( + child: Center( + child: CircularProgressIndicator(), + ), + ), + routerConfig: _router, + ); + }, + ), ); } } diff --git a/app/lib/main/init.dart b/app/lib/main/init.dart index 08c6769..7be81c9 100644 --- a/app/lib/main/init.dart +++ b/app/lib/main/init.dart @@ -6,12 +6,13 @@ import 'package:data/init.dart'; import 'package:domain/init.dart'; import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; -import 'package:url_strategy/url_strategy.dart'; +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; + void init() async { WidgetsFlutterBinding.ensureInitialized(); + usePathUrlStrategy(); await initialize(); - setHashUrlStrategy(); runApp(const App()); } diff --git a/app/lib/presentation/navigation/routers.dart b/app/lib/presentation/navigation/routers.dart index 572a59c..cd18b74 100644 --- a/app/lib/presentation/navigation/routers.dart +++ b/app/lib/presentation/navigation/routers.dart @@ -1,33 +1,137 @@ +import 'package:app/main/init.dart'; import 'package:app/presentation/ui/pages/home/home_page.dart'; import 'package:app/presentation/ui/pages/login/login_page.dart'; import 'package:app/presentation/ui/pages/sign_up/sign_up_page.dart'; import 'package:app/presentation/ui/pages/splash/splash_page.dart'; +import 'package:common/core/resource.dart'; +import 'package:domain/bloc/auth/auth_cubit.dart'; +import 'package:domain/bloc/auth/auth_state.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; +enum Routes { + auth, + login, + signup, + app, + home, + placeholder; + + String get path => '/$name'; + String get subPath => name; + + void nav(BuildContext context, {Object? extra}) { + context.router.goNamed( + name, + extra: extra, + ); + } + + static GoRouter init(BuildContext context, {String? initialLocation}) => + Routers.appRouter(context, initialLocation: initialLocation); +} + +extension ContextOnRouter on BuildContext { + GoRouter get router => GoRouter.of(this); +} + class Routers { - static GoRouter authRouter = GoRouter( - initialLocation: "/splash", - routes: [ - GoRoute( - name: "login", - path: "/login", - builder: (context, state) => const LoginPage(), - ), - GoRoute( - name: "splash", - path: "/splash", - builder: (context, state) => const SplashPage(), - ), - GoRoute( - name: "signUp", - path: "/signUp", - builder: (context, state) => const SignUpPage(), - ), - GoRoute( - name: "home", - path: "/home", - builder: (context, state) => const HomePage(), - ), - ], - ); + static GoRouter appRouter( + BuildContext context, { + String? initialLocation, + }) => + GoRouter( + initialLocation: initialLocation ?? + (getIt().isLoggedIn() + ? Routes.app.path + : Routes.auth.path), + routes: [ + GoRoute( + path: '/', + builder: (context, state) { + return BlocListener( + listenWhen: (previous, current) => current is RSuccess, + listener: (_, appState) { + if (appState is RSuccess) { + switch (appState.data) { + case AuthStateAuthenticated _: + debugPrint('User is authenticated: ${state.fullPath}'); + if (state.fullPath?.startsWith(Routes.app.path) ?? + false) { + // Already navigating to app, do nothing + return; + } + debugPrint('Navigating to app route'); + Routes.app.nav(context); + break; + case AuthStateUnauthenticated _: + debugPrint( + 'User is unauthenticated: ${state.fullPath}'); + if (state.fullPath?.startsWith(Routes.auth.path) ?? + false) { + // Already navigating to auth, do nothing + return; + } + debugPrint('Navigating to auth route'); + Routes.auth.nav(context); + break; + case _: + } + } + }, + child: const SplashPage(), + ); + }, + routes: [ + ShellRoute( + builder: (context, state, child) => child, + routes: [ + GoRoute( + name: Routes.auth.name, + path: Routes.auth.path, + redirect: (context, state) { + if (getIt().isLoggedIn()) { + return Routes.app.path; + } + return null; + }, + builder: (context, state) => const LoginPage(), + routes: [ + GoRoute( + name: Routes.signup.name, + path: Routes.signup.subPath, + builder: (context, state) => const SignUpPage(), + ), + ], + ), + ], + ), + ShellRoute( + builder: (context, state, child) => child, + routes: [ + GoRoute( + name: Routes.app.name, + path: Routes.app.path, + redirect: (context, state) { + if (!getIt().isLoggedIn()) { + return Routes.auth.path; + } + return null; + }, + builder: (context, state) => const HomePage(), + routes: [ + GoRoute( + name: Routes.placeholder.name, + path: Routes.placeholder.subPath, + builder: (context, state) => const Placeholder(), + ), + ], + ), + ], + ), + ], + ), + ], + ); } diff --git a/app/lib/presentation/ui/pages/home/home_view.dart b/app/lib/presentation/ui/pages/home/home_view.dart index 1edcc29..f0d9eb8 100644 --- a/app/lib/presentation/ui/pages/home/home_view.dart +++ b/app/lib/presentation/ui/pages/home/home_view.dart @@ -14,6 +14,8 @@ class HomeView extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( + title: const Text('Home'), + automaticallyImplyLeading: false, actions: [ IconButton( onPressed: () => _authCubit.logOut(), diff --git a/app/lib/presentation/ui/pages/login/login_page.dart b/app/lib/presentation/ui/pages/login/login_page.dart index 5c9b666..6ad41be 100644 --- a/app/lib/presentation/ui/pages/login/login_page.dart +++ b/app/lib/presentation/ui/pages/login/login_page.dart @@ -1,6 +1,6 @@ import 'package:app/main/init.dart'; +import 'package:app/presentation/navigation/routers.dart'; import 'package:app/presentation/resources/resources.dart'; -import 'package:app/presentation/themes/app_themes.dart'; import 'package:app/presentation/ui/custom/app_theme_switch.dart'; import 'package:app/presentation/ui/custom/loading_screen.dart'; import 'package:common/core/resource.dart'; @@ -33,10 +33,7 @@ class LoginPage extends StatelessWidget { child: ElevatedButton( child: const Text('Login'), onPressed: () { - _authCubit.login( - 'Rootstrap', - '12345678', - ); + _authCubit.login('Rootstrap', '12345678'); }, ), ), diff --git a/app/lib/presentation/ui/pages/splash/splash_page.dart b/app/lib/presentation/ui/pages/splash/splash_page.dart index 9e8a0aa..9b8f1f4 100644 --- a/app/lib/presentation/ui/pages/splash/splash_page.dart +++ b/app/lib/presentation/ui/pages/splash/splash_page.dart @@ -3,7 +3,8 @@ import 'package:domain/bloc/auth/auth_cubit.dart'; import 'package:flutter/material.dart'; class SplashPage extends StatefulWidget { - const SplashPage({super.key}); + final bool instant; + const SplashPage({super.key, this.instant = true}); @override State createState() => _SplashPageState(); @@ -20,7 +21,7 @@ class _SplashPageState extends State { /// Add post frame callback to avoid calling bloc methods during build WidgetsBinding.instance.addPostFrameCallback((_) async { - await Future.delayed(const Duration(seconds: 1)); + await Future.delayed(Duration(seconds: widget.instant ? 0 : 2)); _authCubit.onValidate(); }); } diff --git a/app/linux/flutter/generated_plugin_registrant.cc b/app/linux/flutter/generated_plugin_registrant.cc index e71a16d..1ea3346 100644 --- a/app/linux/flutter/generated_plugin_registrant.cc +++ b/app/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) gtk_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin"); + gtk_plugin_register_with_registrar(gtk_registrar); } diff --git a/app/linux/flutter/generated_plugins.cmake b/app/linux/flutter/generated_plugins.cmake index 2e1de87..2ee43a9 100644 --- a/app/linux/flutter/generated_plugins.cmake +++ b/app/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + gtk ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/app/pubspec.yaml b/app/pubspec.yaml index c967b0a..5b422a4 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -18,7 +18,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: '>=3.0.0 <4.0.0' + sdk: ">=3.0.0 <4.0.0" dependencies: flutter: @@ -35,7 +35,7 @@ dependencies: package_info_plus: ^8.3.0 permission_handler: ^11.4.0 universal_html: ^2.2.2 - go_router: ^7.0.1 + go_router: ^17.0.0 equatable: ^2.0.5 firebase_core: ^3.13.0 url_strategy: ^0.2.0 @@ -51,6 +51,10 @@ dependencies: http: ^1.5.0 melos: ^3.4.0 dev: ^1.0.0 + flutter_dotenv: ^5.2.1 + flutter_web_plugins: + sdk: flutter + app_links: ^6.4.1 dev_dependencies: bloc_test: ^9.0.2 diff --git a/modules/domain/lib/bloc/auth/auth_cubit.dart b/modules/domain/lib/bloc/auth/auth_cubit.dart index 27d53b1..fd83f16 100644 --- a/modules/domain/lib/bloc/auth/auth_cubit.dart +++ b/modules/domain/lib/bloc/auth/auth_cubit.dart @@ -31,6 +31,8 @@ class AuthCubit extends BaseCubit { isLogOut(); } + bool isLoggedIn() => _authService.isLoggedIn(); + void isLogin() => isSuccess(AuthStateAuthenticated()); void isLogOut() => isSuccess(AuthStateUnauthenticated()); diff --git a/pubspec.yaml b/pubspec.yaml index 20ca790..b25738f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,6 +11,7 @@ environment: dependencies: flutter: sdk: flutter + app_links: ^6.4.1 dev_dependencies: flutter_test: