// ⏱️ 우리 학교 전용 "백품타" 공부 타이머 화면 (UI 전용). // 서버 통신/상태는 lib/function/study_timer_controller.dart가 담당한다. import 'package:flutter/material.dart'; import '../function/study_timer_controller.dart'; import '../theme/app_palette.dart'; import 'app_notice.dart'; import 'launchpad_transition.dart'; import 'study_ranking_screen.dart'; import 'title_pill.dart'; class StudyTimerScreen extends StatefulWidget { final String studentId; final String studentName; final int? grade; const StudyTimerScreen({ super.key, required this.studentId, required this.studentName, this.grade, }); @override State createState() => _StudyTimerScreenState(); } class _StudyTimerScreenState extends State { late final StudyTimerController _controller; @override void initState() { super.initState(); _controller = StudyTimerController( studentId: widget.studentId, studentName: widget.studentName, ); _controller.init(); } @override void dispose() { _controller.dispose(); super.dispose(); } String _formatHMS(int totalSeconds) { final h = totalSeconds ~/ 3600; final m = (totalSeconds % 3600) ~/ 60; final s = totalSeconds % 60; final hh = h.toString().padLeft(2, '0'); final mm = m.toString().padLeft(2, '0'); final ss = s.toString().padLeft(2, '0'); return '$hh:$mm:$ss'; } String _formatHM(int totalSeconds) { final h = totalSeconds ~/ 3600; final m = (totalSeconds % 3600) ~/ 60; if (h > 0) return '$h시간 $m분'; return '$m분'; } Future _runAction( Future<(bool success, String message)> Function() action, ) async { final (success, message) = await action(); if (!mounted) return; AppNotice.show( context, message, icon: success ? Icons.check_circle_rounded : Icons.error_outline_rounded, ); } void _openRanking() { if (widget.grade == null) { AppNotice.show(context, '학년 정보가 없어 랭킹을 볼 수 없어요.'); return; } pushLaunchpad( context, (context) => StudyRankingScreen( grade: widget.grade!, myStudentId: widget.studentId, ), ); } @override Widget build(BuildContext context) { return ListenableBuilder( listenable: _controller, builder: (context, _) { final bool isRunning = _controller.isRunning; final bool isPaused = _controller.isPaused; return Scaffold( backgroundColor: AppPalette.mist, appBar: AppBar( backgroundColor: Colors.transparent, foregroundColor: AppPalette.ink, elevation: 0, centerTitle: true, title: const TitlePill('백품타'), actions: [ IconButton( icon: const Icon(Icons.leaderboard_rounded), tooltip: '학년 랭킹', onPressed: _openRanking, ), ], ), body: _controller.isLoading ? const Center(child: CircularProgressIndicator()) : Center( child: SingleChildScrollView( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( isRunning ? '지금 공부 중이에요' : isPaused ? '일시정지 중이에요' : '공부를 시작해볼까요?', style: const TextStyle( fontSize: 15, color: Colors.grey, ), ), const SizedBox(height: 12), Text( _formatHMS(_controller.liveElapsedSeconds), style: TextStyle( fontSize: 52, fontWeight: FontWeight.bold, color: isPaused ? Colors.grey : AppPalette.ink, ), ), const SizedBox(height: 36), _buildActionButtons(isRunning, isPaused), const SizedBox(height: 40), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ _statBox( '오늘', _formatHM(_controller.todaySecondsLive), ), const SizedBox(width: 12), _statBox( '이번주', _formatHM(_controller.weekSecondsLive), ), const SizedBox(width: 12), _statBox( '전체', _formatHM(_controller.totalSecondsLive), ), ], ), ], ), ), ), ); }, ); } Widget _buildActionButtons(bool isRunning, bool isPaused) { if (!isRunning && !isPaused) { return _circleButton( size: 140, icon: Icons.play_arrow_rounded, label: '공부 시작', color: AppPalette.ink, onTap: () => _runAction(_controller.start), ); } return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ _circleButton( size: 110, icon: isPaused ? Icons.play_arrow_rounded : Icons.pause_rounded, label: isPaused ? '재개' : '일시정지', color: isPaused ? AppPalette.ink : Colors.amber[700]!, onTap: () => _runAction(isPaused ? _controller.resume : _controller.pause), ), const SizedBox(width: 20), _circleButton( size: 110, icon: Icons.stop_rounded, label: '공부 종료', color: Colors.red[400]!, onTap: () => _runAction(_controller.stop), ), ], ); } Widget _circleButton({ required double size, required IconData icon, required String label, required Color color, required VoidCallback onTap, }) { return GestureDetector( onTap: _controller.isBusy ? null : onTap, child: Container( width: size, height: size, decoration: BoxDecoration( shape: BoxShape.circle, color: color, boxShadow: [ BoxShadow( color: color.withValues(alpha: 0.3), blurRadius: 20, offset: const Offset(0, 8), ), ], ), child: Center( child: _controller.isBusy ? const CircularProgressIndicator(color: Colors.white) : Column( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, color: Colors.white, size: size > 120 ? 36 : 28), Text( label, style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: size > 120 ? 14 : 12.5, ), ), ], ), ), ), ); } Widget _statBox(String label, String value) { return Container( width: 100, padding: const EdgeInsets.symmetric(vertical: 14), decoration: BoxDecoration( color: AppPalette.paper, borderRadius: BorderRadius.circular(16), border: Border.all(color: AppPalette.sage), ), child: Column( children: [ Text(label, style: TextStyle(color: Colors.grey[600], fontSize: 12)), const SizedBox(height: 4), Text( value, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 15), ), ], ), ); } }