Files
TaskManagerMobile/lib/features/onboarding/screens/onboarding_screen.dart
T

375 lines
12 KiB
Dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.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 {
const OnboardingScreen({super.key});
@override
ConsumerState<OnboardingScreen> createState() => _OnboardingScreenState();
}
class _OnboardingScreenState extends ConsumerState<OnboardingScreen> {
late final PageController _pageController;
late final TextEditingController _nameController;
@override
void initState() {
super.initState();
_pageController = PageController();
_nameController = TextEditingController(text: 'Míša');
}
@override
void dispose() {
_pageController.dispose();
_nameController.dispose();
super.dispose();
}
void _goToPage(int page) {
_pageController.animateToPage(
page,
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
ref.read(onboardingStepProvider.notifier).goTo(page);
}
Future<void> _completeOnboarding() async {
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,
);
ref.invalidate(currentUserProvider);
if (mounted) {
context.go('/tasks');
}
}
@override
Widget build(BuildContext context) {
final step = ref.watch(onboardingStepProvider);
return Scaffold(
backgroundColor: KittieColors.brownDark,
body: SafeArea(
child: Column(
children: [
// Progress dots
Padding(
padding: const EdgeInsets.only(top: 24, bottom: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(3, (i) {
final isActive = i == step;
return AnimatedContainer(
duration: const Duration(milliseconds: 200),
margin: const EdgeInsets.symmetric(horizontal: 4),
width: isActive ? 24 : 8,
height: 8,
decoration: BoxDecoration(
color: isActive
? KittieColors.amber
: KittieColors.brownLight,
borderRadius: BorderRadius.circular(4),
),
);
}),
),
),
// Pages
Expanded(
child: PageView(
controller: _pageController,
physics: const NeverScrollableScrollPhysics(),
onPageChanged: (page) {
ref.read(onboardingStepProvider.notifier).goTo(page);
},
children: [
_WelcomeStep(onNext: () => _goToPage(1)),
_PetSelectionStep(
nameController: _nameController,
onNext: () => _goToPage(2),
onBack: () => _goToPage(0),
),
_ReadyStep(
onComplete: _completeOnboarding,
onBack: () => _goToPage(1),
),
],
),
),
],
),
),
);
}
}
// ─── Step 1: Welcome ────────────────────────────────────────────────────────
class _WelcomeStep extends StatelessWidget {
const _WelcomeStep({required this.onNext});
final VoidCallback onNext;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('🐱', style: TextStyle(fontSize: 80)),
const SizedBox(height: 24),
Text(
'Vítej v KittieMobile',
style: KittieTypography.h1.copyWith(color: KittieColors.cream),
textAlign: TextAlign.center,
),
const SizedBox(height: 12),
Text(
'Tvůj mazlíček ti pomůže zvládnout úkoly.',
style:
KittieTypography.bodyLarge.copyWith(color: KittieColors.amber),
textAlign: TextAlign.center,
),
const SizedBox(height: 48),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: onNext,
style: ElevatedButton.styleFrom(
backgroundColor: KittieColors.amber,
foregroundColor: KittieColors.brownDark,
),
child: const Text('Začít'),
),
),
],
),
);
}
}
// ─── Step 2: Pet Selection ──────────────────────────────────────────────────
class _PetSelectionStep extends ConsumerWidget {
const _PetSelectionStep({
required this.nameController,
required this.onNext,
required this.onBack,
});
final TextEditingController nameController;
final VoidCallback onNext;
final VoidCallback onBack;
static const _pets = [
(type: PetType.dog, emoji: '🐶', label: 'Pes'),
(type: PetType.cat, emoji: '🐱', label: 'Kočka'),
(type: PetType.capybara, emoji: '🦫', label: 'Kapybara'),
];
@override
Widget build(BuildContext context, WidgetRef ref) {
final selected = ref.watch(selectedPetTypeProvider);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Vyber si mazlíčka',
style: KittieTypography.h2.copyWith(color: KittieColors.cream),
),
const SizedBox(height: 32),
// Pet grid
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: _pets.map((pet) {
final isSelected = selected == pet.type;
return GestureDetector(
onTap: () {
ref.read(selectedPetTypeProvider.notifier).select(pet.type);
},
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
width: 80,
height: 80,
margin: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
color: KittieColors.brownMid,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color:
isSelected ? KittieColors.amber : Colors.transparent,
width: 2,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(pet.emoji, style: const TextStyle(fontSize: 32)),
const SizedBox(height: 4),
Text(
pet.label,
style: KittieTypography.labelSmall
.copyWith(color: KittieColors.cream),
),
],
),
),
);
}).toList(),
),
const SizedBox(height: 32),
// Name input
Text(
'Jak se bude jmenovat?',
style:
KittieTypography.bodyLarge.copyWith(color: KittieColors.cream),
),
const SizedBox(height: 12),
TextField(
controller: nameController,
style: KittieTypography.bodyLarge
.copyWith(color: KittieColors.brownDark),
textAlign: TextAlign.center,
decoration: InputDecoration(
fillColor: KittieColors.creamDark,
hintText: 'Míša',
hintStyle: KittieTypography.bodyLarge
.copyWith(color: KittieColors.brownLight),
),
onChanged: (value) {
ref.read(petNameProvider.notifier).update(value);
},
),
const SizedBox(height: 32),
// Navigation buttons
Row(
children: [
TextButton(
onPressed: onBack,
child: Text(
'Zpět',
style: KittieTypography.labelLarge
.copyWith(color: KittieColors.brownLight),
),
),
const Spacer(),
ElevatedButton(
onPressed: onNext,
style: ElevatedButton.styleFrom(
backgroundColor: KittieColors.amber,
foregroundColor: KittieColors.brownDark,
),
child: const Text('Pokračovat'),
),
],
),
],
),
);
}
}
// ─── Step 3: Ready ──────────────────────────────────────────────────────────
class _ReadyStep extends ConsumerWidget {
const _ReadyStep({required this.onComplete, required this.onBack});
final VoidCallback onComplete;
final VoidCallback onBack;
@override
Widget build(BuildContext context, WidgetRef ref) {
final petName = ref.watch(petNameProvider);
final petType = ref.watch(selectedPetTypeProvider);
final emoji = switch (petType) {
PetType.dog => '🐶',
PetType.cat => '🐱',
PetType.capybara => '🦫',
};
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(
Icons.check_circle_rounded,
color: KittieColors.sageGreen,
size: 80,
),
const SizedBox(height: 24),
Text(
'Vše je připraveno!',
style: KittieTypography.h1.copyWith(color: KittieColors.cream),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
Text(
'$emoji $petName je připraven/a ti pomáhat.',
style:
KittieTypography.bodyLarge.copyWith(color: KittieColors.amber),
textAlign: TextAlign.center,
),
const SizedBox(height: 48),
Row(
children: [
TextButton(
onPressed: onBack,
child: Text(
'Zpět',
style: KittieTypography.labelLarge
.copyWith(color: KittieColors.brownLight),
),
),
const Spacer(),
ElevatedButton(
onPressed: onComplete,
style: ElevatedButton.styleFrom(
backgroundColor: KittieColors.sageGreen,
foregroundColor: KittieColors.cream,
),
child: const Text('Začít používat'),
),
],
),
],
),
);
}
}