Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 74 additions & 38 deletions app/lib/main/app.dart
Original file line number Diff line number Diff line change
@@ -1,63 +1,99 @@
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<App> createState() => _AppState();
}

class _AppState extends State<App> {
late final GoRouter _router;
bool _isRouterReady = false;

@override
void initState() {
super.initState();
_initRouter();
}

Future<void> _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(
providers: [
BlocProvider(create: (_) => getIt<AppCubit>()),
BlocProvider(create: (_) => getIt<AuthCubit>()),
],
child: BlocBuilder<AppCubit, AppState>(
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<AuthCubit, Resource>(
listener: (_, state) {
if (state is RSuccess<AuthState>) {
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<AppCubit, AppState>(
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,
);
},
),
);
}
}
Expand Down
5 changes: 3 additions & 2 deletions app/lib/main/init.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The usePathUrlStrategy() call should be placed after WidgetsFlutterBinding.ensureInitialized() but before any other initialization. However, it's currently placed after the binding initialization but on a separate line without a comment. While functionally correct, consider adding a comment explaining why path-based URLs are being used instead of hash-based URLs (e.g., for better SEO, cleaner URLs).

Suggested change
WidgetsFlutterBinding.ensureInitialized();
WidgetsFlutterBinding.ensureInitialized();
// Use path-based URLs instead of hash-based URLs for better SEO and cleaner URLs.

Copilot uses AI. Check for mistakes.
usePathUrlStrategy();
await initialize();
setHashUrlStrategy();
runApp(const App());
}

Expand Down
154 changes: 129 additions & 25 deletions app/lib/presentation/navigation/routers.dart
Original file line number Diff line number Diff line change
@@ -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';
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The path getter returns '/$name' which assumes the route is always at the root level. This creates inconsistency when used in nested route structures (as seen in the usage on lines 59 and 83). Consider renaming this to rootPath to make its intent clear, and use subPath for nested routes.

Copilot uses AI. Check for mistakes.
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<AuthCubit>().isLoggedIn()
? Routes.app.path
: Routes.auth.path),
routes: [
GoRoute(
path: '/',
builder: (context, state) {
return BlocListener<AuthCubit, Resource>(
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<AuthCubit>().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<AuthCubit>().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(),
),
],
),
],
),
],
),
],
);
}
2 changes: 2 additions & 0 deletions app/lib/presentation/ui/pages/home/home_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
7 changes: 2 additions & 5 deletions app/lib/presentation/ui/pages/login/login_page.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -33,10 +33,7 @@ class LoginPage extends StatelessWidget {
child: ElevatedButton(
child: const Text('Login'),
onPressed: () {
_authCubit.login(
'Rootstrap',
'12345678',
);
_authCubit.login('Rootstrap', '12345678');
},
),
),
Expand Down
5 changes: 3 additions & 2 deletions app/lib/presentation/ui/pages/splash/splash_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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<SplashPage> createState() => _SplashPageState();
Expand All @@ -20,7 +21,7 @@ class _SplashPageState extends State<SplashPage> {

/// 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();
});
}
Expand Down
4 changes: 4 additions & 0 deletions app/linux/flutter/generated_plugin_registrant.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

#include "generated_plugin_registrant.h"

#include <gtk/gtk_plugin.h>

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);
}
1 change: 1 addition & 0 deletions app/linux/flutter/generated_plugins.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#

list(APPEND FLUTTER_PLUGIN_LIST
gtk
)

list(APPEND FLUTTER_FFI_PLUGIN_LIST
Expand Down
Loading