m3mo 04863ff008 Add setup and onboarding screens
- Create SetupPage for choosing local or online mode
- Create OnboardingPage with 3-slide tutorial
- Explain Eisenhower Method and ALPEN planning approach
2026-02-03 14:21:58 +01:00

199 lines
5.5 KiB
Dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import '../../../../l10n/app_localizations.dart';
import '../../../settings/presentation/viewmodels/settings_viewmodel.dart';
class OnboardingPage extends StatefulWidget {
const OnboardingPage({super.key});
@override
State<OnboardingPage> createState() => _OnboardingPageState();
}
class _OnboardingPageState extends State<OnboardingPage> {
final PageController _pageController = PageController();
int _currentPage = 0;
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
Future<void> _completeOnboarding() async {
final settingsVm = context.read<SettingsViewModel>();
await settingsVm.setOnboardingShown(true);
if (mounted) {
// Navigate based on app mode
if (settingsVm.isOnlineMode) {
context.go('/login');
} else {
context.go('/');
}
}
}
void _nextPage() {
if (_currentPage < 2) {
_pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
} else {
_completeOnboarding();
}
}
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final theme = Theme.of(context);
final pages = [
_OnboardingSlide(
icon: Icons.schedule,
title: l10n.onboardingTitle1,
description: l10n.onboardingDesc1,
),
_OnboardingSlide(
icon: Icons.psychology,
title: l10n.onboardingTitle2,
description: l10n.onboardingDesc2,
),
_OnboardingSlide(
icon: Icons.rocket_launch,
title: l10n.onboardingTitle3,
description: l10n.onboardingDesc3,
),
];
return Scaffold(
body: SafeArea(
child: Column(
children: [
// Skip button
Align(
alignment: Alignment.topRight,
child: Padding(
padding: const EdgeInsets.all(16),
child: TextButton(
onPressed: _completeOnboarding,
child: Text(l10n.skip),
),
),
),
// Page content
Expanded(
child: PageView(
controller: _pageController,
onPageChanged: (page) {
setState(() {
_currentPage = page;
});
},
children: pages,
),
),
// Page indicator
Padding(
padding: const EdgeInsets.symmetric(vertical: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
pages.length,
(index) => AnimatedContainer(
duration: const Duration(milliseconds: 200),
margin: const EdgeInsets.symmetric(horizontal: 4),
width: _currentPage == index ? 24 : 8,
height: 8,
decoration: BoxDecoration(
color: _currentPage == index
? theme.colorScheme.primary
: theme.colorScheme.outlineVariant,
borderRadius: BorderRadius.circular(4),
),
),
),
),
),
// Next/Get Started button
Padding(
padding: const EdgeInsets.fromLTRB(24, 0, 24, 32),
child: SizedBox(
width: double.infinity,
child: FilledButton(
onPressed: _nextPage,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Text(
_currentPage == 2 ? l10n.getStarted : l10n.next,
style: const TextStyle(fontSize: 16),
),
),
),
),
),
],
),
),
);
}
}
class _OnboardingSlide extends StatelessWidget {
final IconData icon;
final String title;
final String description;
const _OnboardingSlide({
required this.icon,
required this.title,
required this.description,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: theme.colorScheme.primaryContainer,
shape: BoxShape.circle,
),
child: Icon(
icon,
size: 64,
color: theme.colorScheme.onPrimaryContainer,
),
),
const SizedBox(height: 48),
Text(
title,
style: theme.textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
Text(
description,
style: theme.textTheme.bodyLarge?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
textAlign: TextAlign.center,
),
],
),
);
}
}