From 88c5ffe51f97a3e696f2eb37b6e738b77074750e Mon Sep 17 00:00:00 2001 From: TrochtaOndrej Date: Wed, 18 Mar 2026 21:15:22 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20User/onboarding=20persistence=20?= =?UTF-8?q?=E2=80=94=20save=20to=20DB,=20load=20on=20startup=20[AITM-233]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/router/app_router.dart | 21 ++++++++++ .../onboarding/screens/onboarding_screen.dart | 27 +++++++++---- .../tasks/providers/pet_providers.dart | 38 +++++++++++++------ .../tasks/providers/task_providers.dart | 6 +-- .../tasks/screens/add_task_screen.dart | 3 +- 5 files changed, 73 insertions(+), 22 deletions(-) diff --git a/lib/core/router/app_router.dart b/lib/core/router/app_router.dart index f48d61e..d33ab4f 100644 --- a/lib/core/router/app_router.dart +++ b/lib/core/router/app_router.dart @@ -1,6 +1,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; +import '../../features/onboarding/screens/onboarding_screen.dart'; +import '../../features/tasks/providers/task_providers.dart'; import '../../features/tasks/screens/tasks_screen.dart'; import '../../features/tasks/screens/pet_screen.dart'; import '../../features/zen_mode/screens/zen_screen.dart'; @@ -9,9 +11,28 @@ import '../../shared/widgets/app_shell.dart'; import 'routes.dart'; final appRouterProvider = Provider((ref) { + final currentUser = ref.watch(currentUserProvider); + return GoRouter( initialLocation: Routes.tasks, + redirect: (context, state) { + final isOnboarding = state.matchedLocation == Routes.onboarding; + + // While loading user from DB, don't redirect + if (currentUser.isLoading) return null; + + final hasUser = currentUser.value != null; + + if (!hasUser && !isOnboarding) return Routes.onboarding; + if (hasUser && isOnboarding) return Routes.tasks; + return null; + }, routes: [ + // Onboarding — outside shell, no bottom nav + GoRoute( + path: Routes.onboarding, + builder: (context, state) => const OnboardingScreen(), + ), StatefulShellRoute.indexedStack( builder: (context, state, navigationShell) { return AppShell(navigationShell: navigationShell); diff --git a/lib/features/onboarding/screens/onboarding_screen.dart b/lib/features/onboarding/screens/onboarding_screen.dart index eec6c00..45802d5 100644 --- a/lib/features/onboarding/screens/onboarding_screen.dart +++ b/lib/features/onboarding/screens/onboarding_screen.dart @@ -1,11 +1,14 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; -import 'package:shared_preferences/shared_preferences.dart'; +import 'package:uuid/uuid.dart'; import '../../../core/theme/kittie_colors.dart'; import '../../../core/theme/kittie_typography.dart'; import '../../../domain/entities/pet_type.dart'; +import '../../tasks/providers/task_providers.dart'; import '../providers/onboarding_providers.dart'; class OnboardingScreen extends ConsumerStatefulWidget { @@ -43,13 +46,23 @@ class _OnboardingScreenState extends ConsumerState { } Future _completeOnboarding() async { - final prefs = await SharedPreferences.getInstance(); - await prefs.setBool('onboarding_complete', true); - await prefs.setString( - 'pet_type', - ref.read(selectedPetTypeProvider).name, + final repo = ref.read(userRepositoryProvider); + final petType = ref.read(selectedPetTypeProvider); + final petName = ref.read(petNameProvider); + + const uuid = Uuid(); + final userId = uuid.v4(); + final deviceId = '${Platform.operatingSystem}-${uuid.v4()}'; + + await repo.createUser( + id: userId, + deviceId: deviceId, + name: 'Friend', + petType: petType, + petName: petName, ); - await prefs.setString('pet_name', ref.read(petNameProvider)); + + ref.invalidate(currentUserProvider); if (mounted) { context.go('/tasks'); diff --git a/lib/features/tasks/providers/pet_providers.dart b/lib/features/tasks/providers/pet_providers.dart index 56e0583..53a2a4a 100644 --- a/lib/features/tasks/providers/pet_providers.dart +++ b/lib/features/tasks/providers/pet_providers.dart @@ -22,18 +22,34 @@ enum PetMood { class UserNotifier extends Notifier { @override - UserEntity build() => UserEntity( - id: '1', - name: 'Friend', - petType: PetType.cat, - petName: 'Yoshi', - createdAt: DateTime.now(), - updatedAt: DateTime.now(), - ); + UserEntity build() { + final currentUser = ref.watch(currentUserProvider).value; + if (currentUser != null) return currentUser; + return UserEntity( + id: '', + name: 'Friend', + petType: PetType.cat, + petName: 'Yoshi', + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + ); + } - void updateName(String name) => state = state.copyWith(name: name); - void updatePetName(String petName) => - state = state.copyWith(petName: petName); + void updateName(String name) { + state = state.copyWith(name: name, updatedAt: DateTime.now()); + _persist(); + } + + void updatePetName(String petName) { + state = state.copyWith(petName: petName, updatedAt: DateTime.now()); + _persist(); + } + + void _persist() { + if (state.id.isEmpty) return; + final repo = ref.read(userRepositoryProvider); + repo.updateUser(state); + } } final userProvider = diff --git a/lib/features/tasks/providers/task_providers.dart b/lib/features/tasks/providers/task_providers.dart index c6f4c5f..9a3d99f 100644 --- a/lib/features/tasks/providers/task_providers.dart +++ b/lib/features/tasks/providers/task_providers.dart @@ -34,12 +34,12 @@ final currentUserProvider = FutureProvider((ref) { // ── Task list (reactive stream from DB) ── -const _userId = 'user-1'; - /// All tasks for the current user, streamed from Drift. final tasksProvider = StreamProvider>((ref) { + final user = ref.watch(currentUserProvider).value; + if (user == null) return Stream.value([]); final repo = ref.watch(taskRepositoryProvider); - return repo.watchTasks(_userId); + return repo.watchTasks(user.id); }); /// Alias for [tasksProvider]. Returns all tasks — no date filtering in MVP. diff --git a/lib/features/tasks/screens/add_task_screen.dart b/lib/features/tasks/screens/add_task_screen.dart index 507e1be..cd789b9 100644 --- a/lib/features/tasks/screens/add_task_screen.dart +++ b/lib/features/tasks/screens/add_task_screen.dart @@ -7,6 +7,7 @@ import '../../../core/theme/kittie_colors.dart'; import '../../../core/theme/kittie_typography.dart'; import '../../../domain/entities/task_entity.dart'; import '../providers/add_task_providers.dart'; +import '../providers/pet_providers.dart'; import '../providers/task_providers.dart'; /// Shows the Add Task bottom sheet. Call from any screen: @@ -59,7 +60,7 @@ class _AddTaskSheetState extends ConsumerState<_AddTaskSheet> { final entity = TaskEntity( id: const Uuid().v4(), - userId: 'user-1', + userId: ref.read(userProvider).id, title: title, description: note.isEmpty ? null : note, energyLevel: energy,