Update router and DI for offline-first flow

- Add setup and onboarding routes to router
- Implement redirect logic for setup, onboarding, and mode checks
- Register local database and task datasource in DI container
- Add mode-aware task repository factory
This commit is contained in:
m3mo 2026-02-03 14:22:19 +01:00
parent 04863ff008
commit 8c837ec9d1
2 changed files with 85 additions and 17 deletions

View File

@ -8,13 +8,17 @@ import '../../features/auth/data/datasources/auth_remote_datasource.dart';
import '../../features/auth/data/repositories/auth_repository_impl.dart'; import '../../features/auth/data/repositories/auth_repository_impl.dart';
import '../../features/auth/domain/repositories/auth_repository.dart'; import '../../features/auth/domain/repositories/auth_repository.dart';
import '../../features/auth/presentation/viewmodels/auth_viewmodel.dart'; import '../../features/auth/presentation/viewmodels/auth_viewmodel.dart';
import '../../features/settings/data/settings_local_datasource.dart';
import '../../features/settings/domain/enums/app_mode.dart';
import '../../features/settings/presentation/viewmodels/settings_viewmodel.dart'; import '../../features/settings/presentation/viewmodels/settings_viewmodel.dart';
import '../../features/tasks/data/datasources/task_local_datasource.dart';
import '../../features/tasks/data/datasources/task_remote_datasource.dart';
import '../../features/tasks/data/repositories/local_task_repository_impl.dart';
import '../../features/tasks/data/repositories/task_repository_impl.dart';
import '../../features/tasks/domain/repositories/task_repository.dart';
import '../../features/tasks/presentation/viewmodels/daily_tasks_viewmodel.dart'; import '../../features/tasks/presentation/viewmodels/daily_tasks_viewmodel.dart';
import '../../features/tasks/presentation/viewmodels/task_form_viewmodel.dart'; import '../../features/tasks/presentation/viewmodels/task_form_viewmodel.dart';
import '../../features/tasks/domain/repositories/task_repository.dart'; import '../database/app_database.dart';
import '../../features/tasks/data/repositories/task_repository_impl.dart';
import '../../features/tasks/data/datasources/task_remote_datasource.dart';
import '../../features/settings/data/settings_local_datasource.dart';
import '../logging/app_logger.dart'; import '../logging/app_logger.dart';
import '../network/authenticated_client.dart'; import '../network/authenticated_client.dart';
@ -64,6 +68,14 @@ Future<void> init() async {
), ),
); );
// Settings Data Source (must be registered before SettingsViewModel)
getIt.registerLazySingleton<SettingsLocalDataSource>(
() => SettingsLocalDataSourceImpl(sharedPreferences: getIt()),
);
// Database (for local mode)
getIt.registerLazySingleton<AppDatabase>(() => AppDatabase());
// Task Data sources // Task Data sources
getIt.registerLazySingleton<TaskRemoteDataSource>( getIt.registerLazySingleton<TaskRemoteDataSource>(
() => TaskRemoteDataSourceImpl( () => TaskRemoteDataSourceImpl(
@ -71,14 +83,28 @@ Future<void> init() async {
client: getIt<http.Client>(), client: getIt<http.Client>(),
), ),
); );
getIt.registerLazySingleton<SettingsLocalDataSource>( getIt.registerLazySingleton<TaskLocalDataSource>(
() => SettingsLocalDataSourceImpl(sharedPreferences: getIt()), () => TaskLocalDataSourceImpl(database: getIt()),
); );
// Repositories // Task Repository (mode-aware)
getIt.registerLazySingleton<TaskRepository>( // We use a factory that checks the current app mode
() => TaskRepositoryImpl(remoteDataSource: getIt(), logger: getIt()), getIt.registerFactory<TaskRepository>(() {
final settingsDataSource = getIt<SettingsLocalDataSource>();
final appMode = settingsDataSource.getAppMode();
if (appMode == AppMode.local) {
return LocalTaskRepositoryImpl(
localDataSource: getIt(),
logger: getIt(),
); );
} else {
return TaskRepositoryImpl(
remoteDataSource: getIt(),
logger: getIt(),
);
}
});
// ViewModels // ViewModels
getIt.registerFactory<DailyTasksViewModel>( getIt.registerFactory<DailyTasksViewModel>(

View File

@ -1,13 +1,17 @@
import 'package:flutter/widgets.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import '../core/di/injection_container.dart'; import '../core/di/injection_container.dart';
import '../features/auth/presentation/pages/login_page.dart'; import '../features/auth/presentation/pages/login_page.dart';
import '../features/auth/presentation/pages/register_page.dart'; import '../features/auth/presentation/pages/register_page.dart';
import '../features/auth/presentation/viewmodels/auth_viewmodel.dart'; import '../features/auth/presentation/viewmodels/auth_viewmodel.dart';
import '../features/tasks/presentation/pages/daily_agenda_page.dart'; import '../features/onboarding/presentation/pages/onboarding_page.dart';
import '../features/tasks/presentation/pages/calendar_page.dart'; import '../features/onboarding/presentation/pages/setup_page.dart';
import '../features/tasks/presentation/pages/task_form_page.dart';
import '../features/settings/presentation/pages/settings_page.dart'; import '../features/settings/presentation/pages/settings_page.dart';
import '../features/settings/presentation/viewmodels/settings_viewmodel.dart';
import '../features/tasks/presentation/pages/calendar_page.dart';
import '../features/tasks/presentation/pages/daily_agenda_page.dart';
import '../features/tasks/presentation/pages/task_form_page.dart';
class AppRouter { class AppRouter {
static GoRouter? _router; static GoRouter? _router;
@ -18,13 +22,41 @@ class AppRouter {
} }
static GoRouter _createRouter() { static GoRouter _createRouter() {
final authViewModel = getIt<AuthViewModel>();
final settingsViewModel = getIt<SettingsViewModel>();
return GoRouter( return GoRouter(
initialLocation: '/', initialLocation: '/',
redirect: (context, state) { redirect: (context, state) {
final authViewModel = getIt<AuthViewModel>(); final location = state.matchedLocation;
// 1. Check if setup is completed
if (!settingsViewModel.setupCompleted) {
if (location != '/setup') {
return '/setup';
}
return null;
}
// 2. Check if onboarding is shown
if (!settingsViewModel.onboardingShown) {
if (location != '/onboarding') {
return '/onboarding';
}
return null;
}
// 3. Local mode - allow access to main app, block auth routes
if (settingsViewModel.isLocalMode) {
if (location == '/login' || location == '/register') {
return '/';
}
return null;
}
// 4. Online mode - existing auth logic
final isAuthenticated = authViewModel.isAuthenticated; final isAuthenticated = authViewModel.isAuthenticated;
final isAuthRoute = final isAuthRoute = location == '/login' || location == '/register';
state.matchedLocation == '/login' || state.matchedLocation == '/register';
if (authViewModel.state == AuthState.initial) { if (authViewModel.state == AuthState.initial) {
return null; return null;
@ -40,8 +72,18 @@ class AppRouter {
return null; return null;
}, },
refreshListenable: getIt<AuthViewModel>(), refreshListenable: Listenable.merge([authViewModel, settingsViewModel]),
routes: [ routes: [
GoRoute(
path: '/setup',
name: 'setup',
builder: (context, state) => const SetupPage(),
),
GoRoute(
path: '/onboarding',
name: 'onboarding',
builder: (context, state) => const OnboardingPage(),
),
GoRoute( GoRoute(
path: '/login', path: '/login',
name: 'login', name: 'login',