import 'dart:async'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import '../theme/app_theme.dart'; import '../models/app_settings.dart'; import '../models/focus_session.dart'; import '../models/history_repository.dart'; import '../widgets/ambient_mesh_background.dart'; import '../widgets/rotary_knob.dart'; import '../widgets/bottom_sheets.dart'; import 'breathe_page.dart'; import 'stats_page.dart'; import 'settings_page.dart'; class HomePage extends StatefulWidget { const HomePage({super.key}); @override State createState() => _HomePageState(); } class _HomePageState extends State with TickerProviderStateMixin { double _setDuration = 25.0; double _currentDuration = 0; bool _isTiming = false; Timer? _timer; final List _tags = ["Deep Work", "Reading", "Meditation", "Creativity"]; int _selectedTagIndex = 0; String? _currentIntent; late AnimationController _breathingController; late AnimationController _scaleController; @override void initState() { super.initState(); _currentDuration = _setDuration * 60; _breathingController = AnimationController( vsync: this, duration: const Duration(seconds: 4), )..repeat(reverse: true); _scaleController = AnimationController( vsync: this, duration: const Duration(milliseconds: 300), lowerBound: 0.95, upperBound: 1.0, ); } @override void dispose() { _timer?.cancel(); _breathingController.dispose(); _scaleController.dispose(); super.dispose(); } void _triggerHaptic() { if (AppSettings.enableHaptics) { HapticFeedback.lightImpact(); } } void _handleStartRequest() { if (_isTiming) { _stopTimer(); } else { _showIntentDialog(); } } void _showIntentDialog() { final TextEditingController intentController = TextEditingController(); showDialog( context: context, builder: (context) { return AlertDialog( backgroundColor: Colors.white.withOpacity(0.95), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)), title: const Text( "Focus Intent", style: TextStyle(fontSize: 20, fontWeight: FontWeight.w800), ), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( "What is your goal?", style: TextStyle(color: AppTheme.textSub, fontSize: 16), ), const SizedBox(height: 16), TextField( controller: intentController, autofocus: true, style: const TextStyle( color: AppTheme.textMain, fontWeight: FontWeight.bold, fontSize: 18, ), decoration: InputDecoration( hintText: "e.g. Design System", hintStyle: TextStyle(color: Colors.grey[400]), filled: true, fillColor: AppTheme.bgLight, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 16, ), ), ), ], ), actions: [ TextButton( onPressed: () { Navigator.pop(context); _startTimer(intent: null); }, child: const Text( "Skip", style: TextStyle(color: AppTheme.textSub), ), ), TextButton( onPressed: () { Navigator.pop(context); _startTimer(intent: intentController.text.trim()); }, child: const Text( "START", style: TextStyle( fontWeight: FontWeight.w900, color: AppTheme.primary, letterSpacing: 1, ), ), ), ], ); }, ); } void _startTimer({String? intent}) { if (AppSettings.enableHaptics) HapticFeedback.heavyImpact(); setState(() { _isTiming = true; _currentIntent = (intent != null && intent.isNotEmpty) ? intent : null; _currentDuration = _setDuration * 60; }); _scaleController.forward(); _timer = Timer.periodic(const Duration(seconds: 1), (timer) { if (_currentDuration > 0) { setState(() => _currentDuration--); } else { _stopTimer(completed: true); } }); } void _stopTimer({bool completed = false}) { _timer?.cancel(); _scaleController.reverse(); setState(() => _isTiming = false); if (completed) { _saveSession(5.0); _showCompletionDialog(); } else { _showRatingSheet(); } } void _saveSession(double rating) { HistoryRepository.addSession(FocusSession( startTime: DateTime.now(), durationMinutes: (_setDuration - _currentDuration / 60).round(), tag: _tags[_selectedTagIndex], intent: _currentIntent, rating: rating, )); } void _showRatingSheet() { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (context) => FocusRatingSheet( onSave: (rating) { _saveSession(rating); }, ), ); } void _showCompletionDialog() { showDialog( context: context, builder: (context) => AlertDialog( backgroundColor: Colors.white.withOpacity(0.95), title: const Text( "Session Complete", textAlign: TextAlign.center, style: TextStyle(fontWeight: FontWeight.w800), ), content: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon( Icons.check_circle_outline, size: 80, color: AppTheme.accent, ), if (_currentIntent != null) ...[ const SizedBox(height: 16), Text( '"${_currentIntent!}"', textAlign: TextAlign.center, style: const TextStyle( fontWeight: FontWeight.w800, fontSize: 18, color: AppTheme.textMain, ), ), const SizedBox(height: 4), const Text( "completed!", style: TextStyle(color: AppTheme.textSub), ), ] ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text( "Done", style: TextStyle(fontWeight: FontWeight.bold), ), ) ], ), ); } @override Widget build(BuildContext context) { final size = MediaQuery.of(context).size; final isSmallScreen = size.height < 700; return Scaffold( extendBodyBehindAppBar: true, body: Stack( children: [ AmbientMeshBackground(isDark: _isTiming), SafeArea( child: Column( children: [ _buildAppBar(), const Spacer(), if (_isTiming && _currentIntent != null) AnimatedOpacity( opacity: 1.0, duration: const Duration(milliseconds: 500), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 30), child: Column( children: [ const Icon( Icons.center_focus_strong, color: AppTheme.accent, size: 28, ), const SizedBox(height: 16), Text( _currentIntent!, textAlign: TextAlign.center, style: const TextStyle( color: Colors.white, fontSize: 28, fontWeight: FontWeight.w800, letterSpacing: 0.5, height: 1.2, shadows: [ Shadow(blurRadius: 15, color: Colors.black) ], ), ), ], ), ), ) else AnimatedOpacity( opacity: _isTiming ? 0.0 : 1.0, duration: const Duration(milliseconds: 500), child: SizedBox( height: 50, child: ListView.separated( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 30), itemCount: _tags.length, separatorBuilder: (_, __) => const SizedBox(width: 12), itemBuilder: (context, index) { final isSelected = _selectedTagIndex == index; return GestureDetector( onTap: () { _triggerHaptic(); setState(() => _selectedTagIndex = index); }, child: AnimatedContainer( duration: const Duration(milliseconds: 300), padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 12, ), decoration: BoxDecoration( color: isSelected ? AppTheme.primary : Colors.white.withOpacity(0.6), borderRadius: BorderRadius.circular(30), boxShadow: isSelected ? AppTheme.softShadow : [], ), child: Text( _tags[index], style: TextStyle( color: isSelected ? Colors.white : AppTheme.textMain, fontWeight: FontWeight.w700, fontSize: 15, ), ), ), ); }, ), ), ), const Spacer(), _buildKnobSection(isSmallScreen), if (AppSettings.strictMode && !_isTiming) Padding( padding: const EdgeInsets.only(top: 30), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: const [ Icon( Icons.screen_rotation, size: 20, color: AppTheme.textSub, ), SizedBox(width: 10), Text( "Flip phone to start", style: TextStyle( color: AppTheme.textSub, fontSize: 16, fontWeight: FontWeight.w700, ), ), ], ), ), const Spacer(), _buildBottomControls(), const SizedBox(height: 40), ], ), ), ], ), ); } Widget _buildAppBar() { return Padding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ IconButton( icon: Icon( Icons.air, color: _isTiming ? Colors.white70 : AppTheme.primary, size: 28, ), onPressed: () => Navigator.push( context, CupertinoPageRoute(builder: (_) => const BreathePage()), ), ), Text( _isTiming ? "F O C U S" : "T E M P O", style: TextStyle( fontSize: 20, fontWeight: FontWeight.w900, letterSpacing: 5, color: _isTiming ? Colors.white : AppTheme.primary, shadows: _isTiming ? [const Shadow(blurRadius: 5, color: Colors.black)] : null, ), ), Row( children: [ IconButton( icon: Icon( Icons.bar_chart, color: _isTiming ? Colors.white70 : AppTheme.primary, size: 28, ), onPressed: () => Navigator.push( context, CupertinoPageRoute(builder: (_) => const StatsPage()), ).then((_) => setState(() {})), ), IconButton( icon: Icon( Icons.settings_outlined, color: _isTiming ? Colors.white70 : AppTheme.primary, size: 28, ), onPressed: () => Navigator.push( context, CupertinoPageRoute(builder: (_) => const SettingsPage()), ).then((_) => setState(() {})), ), ], ), ], ), ); } Widget _buildKnobSection(bool isSmallScreen) { final double size = isSmallScreen ? 240 : 300; final double knobSize = isSmallScreen ? 200 : 260; return Stack( alignment: Alignment.center, children: [ AnimatedBuilder( animation: _breathingController, builder: (context, child) { return Container( width: size, height: size, decoration: BoxDecoration( shape: BoxShape.circle, gradient: RadialGradient( colors: [ (_isTiming ? AppTheme.flowStart : AppTheme.accent) .withOpacity(0.2 * _breathingController.value), Colors.transparent, ], ), ), ); }, ), RotaryKnob( size: knobSize, value: _setDuration, min: 1, max: 120, isTiming: _isTiming, currentSeconds: _currentDuration, onChanged: (val) { setState(() { _setDuration = val; _currentDuration = val * 60; }); }, ), ], ); } Widget _buildBottomControls() { final double progress = (HistoryRepository.todayMinutes / AppSettings.dailyGoalMinutes) .clamp(0.0, 1.0); return Padding( padding: const EdgeInsets.symmetric(horizontal: 40), child: Column( children: [ AnimatedOpacity( opacity: _isTiming ? 0.0 : 1.0, duration: const Duration(milliseconds: 300), child: GestureDetector( onTap: () { showModalBottomSheet( context: context, backgroundColor: Colors.transparent, builder: (context) => const SoundMixerSheet(), ); }, child: Container( padding: const EdgeInsets.symmetric( horizontal: 20, vertical: 12, ), decoration: BoxDecoration( color: Colors.white.withOpacity(0.8), borderRadius: BorderRadius.circular(24), border: Border.all(color: Colors.white), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 4), ) ], ), child: Row( mainAxisSize: MainAxisSize.min, children: const [ Icon(Icons.graphic_eq, size: 18, color: AppTheme.primary), SizedBox(width: 8), Text( "Soundscape Mixer", style: TextStyle( fontSize: 14, fontWeight: FontWeight.w800, color: AppTheme.textMain, ), ), ], ), ), ), ), const SizedBox(height: 30), Stack( alignment: Alignment.center, children: [ if (!_isTiming) SizedBox( width: 92, height: 92, child: CircularProgressIndicator( value: progress, strokeWidth: 4, backgroundColor: Colors.grey.withOpacity(0.2), valueColor: const AlwaysStoppedAnimation( AppTheme.accent, ), strokeCap: StrokeCap.round, ), ), GestureDetector( onTap: _handleStartRequest, child: AnimatedContainer( duration: const Duration(milliseconds: 400), curve: Curves.easeOutCubic, height: 80, width: 80, decoration: BoxDecoration( shape: BoxShape.circle, color: _isTiming ? Colors.red[400] : AppTheme.primary, boxShadow: _isTiming ? [ BoxShadow( color: Colors.red.withOpacity(0.4), blurRadius: 20, spreadRadius: 5, ) ] : AppTheme.softShadow, ), child: Icon( _isTiming ? Icons.stop_rounded : Icons.play_arrow_rounded, color: Colors.white, size: 44, ), ), ), ], ), ], ), ); } }