| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322 |
- import 'package:flutter/material.dart';
- import '../../utils/colors.dart';
- import '../../utils/haptic_utils.dart';
- import '../../utils/settings_manager.dart';
- import '../../l10n/app_localizations.dart';
- class TutorialOverlay extends StatefulWidget {
- final VoidCallback onComplete;
- const TutorialOverlay({super.key, required this.onComplete});
- @override
- State<TutorialOverlay> createState() => _TutorialOverlayState();
- }
- class _TutorialOverlayState extends State<TutorialOverlay>
- with TickerProviderStateMixin {
- late AnimationController _pulseController;
- late AnimationController _fadeController;
- late Animation<double> _pulseAnimation;
- late Animation<double> _fadeAnimation;
- int _currentStep = 0;
- final int _totalSteps = 3;
- @override
- void initState() {
- super.initState();
- _pulseController = AnimationController(
- duration: const Duration(milliseconds: 1500),
- vsync: this,
- )..repeat(reverse: true);
- _fadeController = AnimationController(
- duration: const Duration(milliseconds: 500),
- vsync: this,
- );
- _pulseAnimation = Tween<double>(begin: 0.8, end: 1.2).animate(
- CurvedAnimation(parent: _pulseController, curve: Curves.easeInOut),
- );
- _fadeAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
- CurvedAnimation(parent: _fadeController, curve: Curves.easeInOut),
- );
- _fadeController.forward();
- }
- @override
- void dispose() {
- _pulseController.dispose();
- _fadeController.dispose();
- super.dispose();
- }
- List<TutorialStep> _getSteps(BuildContext context) {
- final l10n = AppLocalizations.of(context)!;
- return [
- TutorialStep(
- title: l10n.tutorialWelcomeTitle,
- description: l10n.tutorialWelcomeDescription,
- icon: Icons.touch_app,
- position: TutorialPosition.center,
- ),
- TutorialStep(
- title: l10n.tutorialRelaxationTitle,
- description: l10n.tutorialRelaxationDescription,
- icon: Icons.stars,
- position: TutorialPosition.center,
- ),
- TutorialStep(
- title: l10n.tutorialZenTitle,
- description: l10n.tutorialZenDescription,
- icon: Icons.self_improvement,
- position: TutorialPosition.center,
- ),
- ];
- }
- @override
- Widget build(BuildContext context) {
- final l10n = AppLocalizations.of(context)!;
- final steps = _getSteps(context);
- final step = steps[_currentStep];
- return FadeTransition(
- opacity: _fadeAnimation,
- child: Container(
- color: ZenColors.black.withValues(alpha: 0.85),
- child: SafeArea(
- child: Stack(
- children: [
- // Tutorial content
- Positioned.fill(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- // Animated icon
- AnimatedBuilder(
- animation: _pulseAnimation,
- builder: (context, child) {
- return Transform.scale(
- scale: _pulseAnimation.value,
- child: Container(
- padding: const EdgeInsets.all(30),
- decoration: BoxDecoration(
- shape: BoxShape.circle,
- color: ZenColors.buttonBackground.withValues(
- alpha: 0.2,
- ),
- border: Border.all(
- color: ZenColors.buttonBackground,
- width: 2,
- ),
- ),
- child: Icon(
- step.icon,
- size: 60,
- color: ZenColors.buttonBackground,
- ),
- ),
- );
- },
- ),
- const SizedBox(height: 40),
- // Title
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 40),
- child: Text(
- step.title,
- style: const TextStyle(
- color: ZenColors.primaryText,
- fontSize: 28,
- fontWeight: FontWeight.bold,
- letterSpacing: 0.5,
- ),
- textAlign: TextAlign.center,
- ),
- ),
- const SizedBox(height: 20),
- // Description
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 40),
- child: Text(
- step.description,
- style: TextStyle(
- color: ZenColors.secondaryText,
- fontSize: 16,
- height: 1.5,
- ),
- textAlign: TextAlign.center,
- ),
- ),
- const SizedBox(height: 60),
- // Progress indicator
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: List.generate(_totalSteps, (index) {
- return Container(
- margin: const EdgeInsets.symmetric(horizontal: 4),
- width: 12,
- height: 12,
- decoration: BoxDecoration(
- shape: BoxShape.circle,
- color:
- index <= _currentStep
- ? ZenColors.buttonBackground
- : ZenColors.mutedText.withValues(
- alpha: 0.3,
- ),
- ),
- );
- }),
- ),
- const SizedBox(height: 40),
- // Action buttons
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 40),
- child: Row(
- children: [
- if (_currentStep > 0)
- Expanded(
- child: OutlinedButton(
- onPressed: _previousStep,
- style: OutlinedButton.styleFrom(
- foregroundColor: ZenColors.primaryText,
- side: const BorderSide(
- color: ZenColors.mutedText,
- ),
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(12),
- ),
- padding: const EdgeInsets.symmetric(
- vertical: 16,
- ),
- ),
- child: Text(
- l10n.previous,
- style: const TextStyle(fontSize: 16),
- ),
- ),
- ),
- if (_currentStep > 0) const SizedBox(width: 16),
- Expanded(
- flex: 2,
- child: ElevatedButton(
- onPressed: _nextStep,
- style: ElevatedButton.styleFrom(
- backgroundColor: ZenColors.buttonBackground,
- foregroundColor: ZenColors.buttonText,
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(12),
- ),
- padding: const EdgeInsets.symmetric(
- vertical: 16,
- ),
- elevation: 4,
- ),
- child: Text(
- _currentStep == _totalSteps - 1
- ? l10n.startRelaxing
- : l10n.continueButton,
- style: const TextStyle(
- fontSize: 16,
- fontWeight: FontWeight.w600,
- ),
- ),
- ),
- ),
- ],
- ),
- ),
- ],
- ),
- ),
- // Skip button
- Positioned(
- top: 20,
- right: 20,
- child: TextButton(
- onPressed: _skipTutorial,
- style: TextButton.styleFrom(
- foregroundColor: ZenColors.mutedText,
- ),
- child: Text(l10n.skip, style: const TextStyle(fontSize: 16)),
- ),
- ),
- ],
- ),
- ),
- ),
- );
- }
- void _nextStep() async {
- if (SettingsManager.isHapticsEnabled) {
- await HapticUtils.vibrate(duration: 50);
- }
- if (_currentStep < _totalSteps - 1) {
- setState(() {
- _currentStep++;
- });
- } else {
- await _completeTutorial();
- }
- }
- void _previousStep() async {
- if (SettingsManager.isHapticsEnabled) {
- await HapticUtils.vibrate(duration: 50);
- }
- if (_currentStep > 0) {
- setState(() {
- _currentStep--;
- });
- }
- }
- void _skipTutorial() async {
- if (SettingsManager.isHapticsEnabled) {
- await HapticUtils.vibrate(duration: 50);
- }
- await _completeTutorial();
- }
- Future<void> _completeTutorial() async {
- await SettingsManager.setTutorialShown(true);
- await _fadeController.reverse();
- widget.onComplete();
- }
- }
- class TutorialStep {
- final String title;
- final String description;
- final IconData icon;
- final TutorialPosition position;
- TutorialStep({
- required this.title,
- required this.description,
- required this.icon,
- required this.position,
- });
- }
- enum TutorialPosition { center, top, bottom }
|