// ๐Ÿ† ํ•™๋…„๋ณ„ ๊ณต๋ถ€์‹œ๊ฐ„ ๋žญํ‚น ํ™”๋ฉด (UI ์ „์šฉ). // ์„œ๋ฒ„ ํ†ต์‹ /์ƒํƒœ๋Š” lib/function/study_ranking_controller.dart๊ฐ€ ๋‹ด๋‹นํ•œ๋‹ค. import 'package:flutter/material.dart'; import '../function/study_ranking_controller.dart'; import '../theme/app_palette.dart'; import 'title_pill.dart'; class StudyRankingScreen extends StatefulWidget { final int grade; final String myStudentId; const StudyRankingScreen({ super.key, required this.grade, required this.myStudentId, }); @override State createState() => _StudyRankingScreenState(); } class _StudyRankingScreenState extends State { late final StudyRankingController _controller; @override void initState() { super.initState(); _controller = StudyRankingController(grade: widget.grade); _controller.init(); } @override void dispose() { _controller.dispose(); super.dispose(); } String _formatHM(int totalSeconds) { final h = totalSeconds ~/ 3600; final m = (totalSeconds % 3600) ~/ 60; if (h > 0) return '$h์‹œ๊ฐ„ $m๋ถ„'; return '$m๋ถ„'; } Color? _rankColor(int rank) { switch (rank) { case 1: return const Color(0xFFC9A227); // ๊ธˆ case 2: return const Color(0xFF9AA0A6); // ์€ case 3: return const Color(0xFFB07A4B); // ๋™ default: return null; } } @override Widget build(BuildContext context) { return ListenableBuilder( listenable: _controller, builder: (context, _) { final ranking = _controller.ranking; return Scaffold( backgroundColor: AppPalette.mist, appBar: AppBar( backgroundColor: Colors.transparent, foregroundColor: AppPalette.ink, elevation: 0, centerTitle: true, title: TitlePill( _controller.scope == 'grade' ? '${widget.grade}ํ•™๋…„ ๊ณต๋ถ€ ๋žญํ‚น' : '์ „์ฒด ๊ณต๋ถ€ ๋žญํ‚น', ), ), body: Column( children: [ Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 0), child: Row( children: [ _scopeChip('grade', '${widget.grade}ํ•™๋…„'), const SizedBox(width: 8), _scopeChip('all', '์ „์ฒด๋žญํ‚น'), ], ), ), Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 8), child: Row( children: [ _periodChip('today', '์˜ค๋Š˜'), const SizedBox(width: 8), _periodChip('week', '์ด๋ฒˆ์ฃผ'), const SizedBox(width: 8), _periodChip('total', '์ „์ฒด'), ], ), ), Expanded( child: _controller.isLoading ? const Center(child: CircularProgressIndicator()) : ranking.isEmpty ? const Center( child: Text( '์•„์ง ๊ธฐ๋ก์ด ์—†์–ด์š”.\n๊ณต๋ถ€ ํƒ€์ด๋จธ๋ฅผ ๋ˆŒ๋Ÿฌ 1๋“ฑ์— ๋„์ „ํ•ด๋ณด์„ธ์š”.', textAlign: TextAlign.center, style: TextStyle(color: Colors.grey), ), ) : ListView.builder( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 8, ), itemCount: ranking.length, itemBuilder: (context, index) { final r = ranking[index]; final bool isMe = r['studentId'] == widget.myStudentId; final int rank = (r['rank'] as num).toInt(); final medalColor = _rankColor(rank); return Container( margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), decoration: BoxDecoration( color: isMe ? AppPalette.ink : AppPalette.paper, borderRadius: BorderRadius.circular(14), border: Border.all( color: isMe ? AppPalette.ink : AppPalette.sage, ), ), child: Row( children: [ SizedBox( width: 32, child: medalColor != null ? Icon( Icons.emoji_events_rounded, color: medalColor, size: 22, ) : Text( '$rank', style: TextStyle( fontWeight: FontWeight.bold, color: isMe ? Colors.white : AppPalette.ink, ), ), ), const SizedBox(width: 8), Expanded( child: Text( r['name'] ?? '', maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( fontWeight: FontWeight.bold, color: isMe ? Colors.white : AppPalette.ink, ), ), ), Text( _formatHM((r['totalSeconds'] as num).toInt()), style: TextStyle( fontWeight: FontWeight.bold, color: isMe ? Colors.white : AppPalette.ink, ), ), ], ), ); }, ), ), ], ), ); }, ); } Widget _periodChip(String value, String label) { final bool selected = _controller.period == value; return Expanded( child: ChoiceChip( label: Center(child: Text(label)), selected: selected, onSelected: (_) => _controller.setPeriod(value), selectedColor: AppPalette.ink, labelStyle: TextStyle( color: selected ? Colors.white : AppPalette.ink, fontWeight: FontWeight.bold, ), ), ); } Widget _scopeChip(String value, String label) { final bool selected = _controller.scope == value; return Expanded( child: ChoiceChip( label: Center(child: Text(label, style: const TextStyle(fontSize: 13))), selected: selected, onSelected: (_) => _controller.setScope(value), selectedColor: AppPalette.ink, backgroundColor: AppPalette.mist, labelStyle: TextStyle( color: selected ? Colors.white : Colors.grey[600], fontWeight: FontWeight.bold, ), ), ); } }