Files
FocusBuddy/lib/screens/onboarding_screen.dart
2025-11-24 10:33:09 +08:00

244 lines
6.6 KiB
Dart

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../theme/app_colors.dart';
import '../theme/app_text_styles.dart';
import 'home_screen.dart';
import '../services/encouragement_service.dart';
/// Onboarding Screen - Shows on first launch
class OnboardingScreen extends StatefulWidget {
final EncouragementService encouragementService;
const OnboardingScreen({
super.key,
required this.encouragementService,
});
/// Check if user has completed onboarding
static Future<bool> hasCompletedOnboarding() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getBool(_onboardingKey) ?? false;
}
/// Mark onboarding as completed
static Future<void> setOnboardingCompleted() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(_onboardingKey, true);
}
static const String _onboardingKey = 'onboarding_completed';
@override
State<OnboardingScreen> createState() => _OnboardingScreenState();
}
class _OnboardingScreenState extends State<OnboardingScreen> {
final PageController _pageController = PageController();
int _currentPage = 0;
final List<OnboardingPage> _pages = [
OnboardingPage(
emoji: '💚',
title: 'Focus without guilt',
description:
"This app is different — it won't punish you for losing focus.\n\nPerfect for ADHD, anxiety, or anyone who finds traditional timers too harsh.",
),
OnboardingPage(
emoji: '🤚',
title: 'Tap when you get distracted',
description:
"We'll gently remind you to come back.\n\nNo shame. No stress. Just a friendly nudge.",
),
OnboardingPage(
emoji: '📊',
title: 'Track your progress',
description:
"See how you're improving, one session at a time.\n\nEvery distraction is just data — not failure.",
),
];
void _onPageChanged(int page) {
setState(() {
_currentPage = page;
});
}
void _nextPage() {
if (_currentPage < _pages.length - 1) {
_pageController.animateToPage(
_currentPage + 1,
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
} else {
_completeOnboarding();
}
}
void _skipOnboarding() {
_completeOnboarding();
}
Future<void> _completeOnboarding() async {
await OnboardingScreen.setOnboardingCompleted();
if (!mounted) return;
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => HomeScreen(
encouragementService: widget.encouragementService,
),
),
);
}
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.background,
body: SafeArea(
child: Column(
children: [
// Skip button
Align(
alignment: Alignment.topRight,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: TextButton(
onPressed: _skipOnboarding,
child: const Text(
'Skip',
style: TextStyle(
fontFamily: 'Nunito',
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.textSecondary,
),
),
),
),
),
// PageView
Expanded(
child: PageView.builder(
controller: _pageController,
onPageChanged: _onPageChanged,
itemCount: _pages.length,
itemBuilder: (context, index) {
return _buildPage(_pages[index]);
},
),
),
// Page indicators
Padding(
padding: const EdgeInsets.symmetric(vertical: 24.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
_pages.length,
(index) => _buildIndicator(index == _currentPage),
),
),
),
// Next/Get Started button
Padding(
padding: const EdgeInsets.all(24.0),
child: SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _nextPage,
child: Text(
_currentPage == _pages.length - 1
? 'Get Started'
: 'Next',
),
),
),
),
],
),
),
);
}
Widget _buildPage(OnboardingPage page) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 32.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Emoji
Text(
page.emoji,
style: const TextStyle(fontSize: 80),
),
const SizedBox(height: 48),
// Title
Text(
page.title,
style: const TextStyle(
fontFamily: 'Nunito',
fontSize: 32,
fontWeight: FontWeight.w700,
color: AppColors.textPrimary,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
// Description
Text(
page.description,
style: AppTextStyles.bodyText.copyWith(
fontSize: 18,
height: 1.6,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 40),
],
),
);
}
Widget _buildIndicator(bool isActive) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 4),
width: isActive ? 24 : 8,
height: 8,
decoration: BoxDecoration(
color: isActive ? AppColors.primary : AppColors.divider,
borderRadius: BorderRadius.circular(4),
),
);
}
}
/// Data class for onboarding page
class OnboardingPage {
final String emoji;
final String title;
final String description;
OnboardingPage({
required this.emoji,
required this.title,
required this.description,
});
}