// ๐Ÿ… ๋ฝ€๋ชจ๋„๋กœ ๋ฃจํ‹ด ๊ณต์œ  ํ™”๋ฉด (UI ์ „์šฉ). ๋‹ค๋ฅธ ํ•™์ƒ๋“ค์ด ์˜ฌ๋ฆฐ ๊ณต๋ถ€/ํœด์‹ ์‹œ๊ฐ„ ์กฐํ•ฉ์„ ๋ณด๊ณ  // ๊ทธ๋Œ€๋กœ ์ ์šฉํ•˜๊ฑฐ๋‚˜, ๋‚ด๊ฐ€ ์“ฐ๋Š” ์กฐํ•ฉ์„ ์ด๋ฆ„ ๋ถ™์—ฌ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋‹ค. // ์„œ๋ฒ„ ํ†ต์‹ /์ƒํƒœ๋Š” lib/function/study_preset_controller.dart๊ฐ€ ๋‹ด๋‹นํ•œ๋‹ค. import 'package:flutter/material.dart'; import '../function/study_preset_controller.dart'; import '../theme/app_palette.dart'; import 'app_notice.dart'; import 'title_pill.dart'; class StudyPresetScreen extends StatefulWidget { final String studentId; final String studentName; final int initialStudyMinutes; final int initialBreakMinutes; const StudyPresetScreen({ super.key, required this.studentId, required this.studentName, required this.initialStudyMinutes, required this.initialBreakMinutes, }); @override State createState() => _StudyPresetScreenState(); } class _StudyPresetScreenState extends State { late final StudyPresetController _controller; @override void initState() { super.initState(); _controller = StudyPresetController( studentId: widget.studentId, studentName: widget.studentName, ); _controller.init(); } @override void dispose() { _controller.dispose(); super.dispose(); } Future _apply(dynamic preset) async { await _controller.markUsed(preset['id'] as int); if (!mounted) return; Navigator.of(context).pop({ 'studyMinutes': preset['studyMinutes'] as int, 'breakMinutes': preset['breakMinutes'] as int, }); } Future _delete(int presetId) async { final (_, message) = await _controller.delete(presetId); if (!mounted) return; AppNotice.show(context, message); } Future _showShareDialog() async { final titleController = TextEditingController(); int studyMinutes = widget.initialStudyMinutes; int breakMinutes = widget.initialBreakMinutes; await showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: AppPalette.paper, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), builder: (sheetContext) { return StatefulBuilder( builder: (sheetContext, setSheetState) { return Padding( padding: EdgeInsets.only( left: 20, right: 20, top: 20, bottom: MediaQuery.of(sheetContext).viewInsets.bottom + 20, ), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const Text( '๋‚ด ๋ฃจํ‹ด ๊ณต์œ ํ•˜๊ธฐ', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), ), const SizedBox(height: 12), TextField( controller: titleController, maxLength: 30, decoration: InputDecoration( hintText: '๋ฃจํ‹ด ์ด๋ฆ„ (์˜ˆ: ๋ถˆํƒœ์šฐ๋Š” ๊ธˆ์š”์ผ)', filled: true, fillColor: AppPalette.mist, border: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide.none, ), ), ), _minuteRow( label: '๊ณต๋ถ€ ์‹œ๊ฐ„', minutes: studyMinutes, onChanged: (v) => setSheetState(() => studyMinutes = v), ), _minuteRow( label: 'ํœด์‹ ์‹œ๊ฐ„', minutes: breakMinutes, onChanged: (v) => setSheetState(() => breakMinutes = v), ), const SizedBox(height: 12), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: AppPalette.ink, foregroundColor: AppPalette.paper, padding: const EdgeInsets.symmetric(vertical: 14), ), onPressed: () async { final (success, message) = await _controller.share( title: titleController.text, studyMinutes: studyMinutes, breakMinutes: breakMinutes, ); if (sheetContext.mounted) { Navigator.of(sheetContext).pop(); } if (!mounted) return; AppNotice.show( context, message, icon: success ? Icons.check_circle_rounded : Icons.error_outline_rounded, ); }, child: const Text('๊ณต์œ ํ•˜๊ธฐ'), ), ], ), ); }, ); }, ); } Widget _minuteRow({ required String label, required int minutes, required ValueChanged onChanged, }) { return Row( children: [ Expanded( child: Text( label, style: const TextStyle(fontWeight: FontWeight.w600), ), ), IconButton( icon: const Icon(Icons.remove_circle_outline_rounded), onPressed: minutes > 5 ? () => onChanged(minutes - 5) : null, ), SizedBox( width: 48, child: Text( '$minutes๋ถ„', textAlign: TextAlign.center, style: const TextStyle(fontWeight: FontWeight.bold), ), ), IconButton( icon: const Icon(Icons.add_circle_outline_rounded), onPressed: minutes < 120 ? () => onChanged(minutes + 5) : null, ), ], ); } @override Widget build(BuildContext context) { return ListenableBuilder( listenable: _controller, builder: (context, _) { final presets = _controller.presets; return Scaffold( backgroundColor: AppPalette.mist, appBar: AppBar( backgroundColor: Colors.transparent, foregroundColor: AppPalette.ink, elevation: 0, centerTitle: true, title: const TitlePill('๋ฝ€๋ชจ๋„๋กœ ๋ฃจํ‹ด ๊ณต์œ '), ), floatingActionButton: FloatingActionButton.extended( backgroundColor: AppPalette.ink, foregroundColor: AppPalette.paper, onPressed: _showShareDialog, icon: const Icon(Icons.ios_share_rounded), label: const Text('๋‚ด ๋ฃจํ‹ด ๊ณต์œ '), ), body: Column( children: [ Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 8), child: Row( children: [ _sortChip('popular', '์ธ๊ธฐ์ˆœ'), const SizedBox(width: 8), _sortChip('recent', '์ตœ์‹ ์ˆœ'), ], ), ), Expanded( child: _controller.isLoading ? const Center(child: CircularProgressIndicator()) : presets.isEmpty ? const Center( child: Text( '์•„์ง ๊ณต์œ ๋œ ๋ฃจํ‹ด์ด ์—†์–ด์š”.\n์ฒซ ๋ฃจํ‹ด์„ ๊ณต์œ ํ•ด๋ณด์„ธ์š”.', textAlign: TextAlign.center, style: TextStyle(color: Colors.grey), ), ) : ListView.builder( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 8, ), itemCount: presets.length, itemBuilder: (context, index) { final preset = presets[index]; final bool isMine = preset['studentId'] == widget.studentId; return Container( margin: const EdgeInsets.only(bottom: 10), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AppPalette.paper, borderRadius: BorderRadius.circular(14), border: Border.all(color: AppPalette.sage), ), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( preset['title'] ?? '', style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 15, ), ), const SizedBox(height: 4), Text( '๊ณต๋ถ€ ${preset['studyMinutes']}๋ถ„ ยท ํœด์‹ ${preset['breakMinutes']}๋ถ„', style: const TextStyle( color: AppPalette.ink, fontSize: 13, ), ), const SizedBox(height: 4), Text( '${preset['studentName'] ?? ''} ยท ์‚ฌ์šฉ ${preset['useCount'] ?? 0}ํšŒ', style: const TextStyle( color: Colors.grey, fontSize: 11.5, ), ), ], ), ), if (isMine) IconButton( icon: const Icon( Icons.delete_outline_rounded, color: Colors.grey, ), onPressed: () => _delete(preset['id'] as int), ), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: AppPalette.ink, foregroundColor: AppPalette.paper, ), onPressed: () => _apply(preset), child: const Text('์ ์šฉ'), ), ], ), ); }, ), ), ], ), ); }, ); } Widget _sortChip(String value, String label) { final bool selected = _controller.sort == value; return ChoiceChip( label: Text(label), selected: selected, onSelected: (_) => _controller.setSort(value), selectedColor: AppPalette.ink, labelStyle: TextStyle( color: selected ? Colors.white : AppPalette.ink, fontWeight: FontWeight.bold, ), ); } }