// ⏱️ 우리 학교 전용 "백품타" 공부 타이머 화면의 기능(서버 통신/상태) 담당 컨트롤러. import 'dart:async'; import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import '../config.dart'; class StudyTimerController extends ChangeNotifier { final String studentId; final String studentName; StudyTimerController({required this.studentId, required this.studentName}); bool _isLoading = true; bool _isBusy = false; // 시작/종료 버튼 눌러서 서버 응답 기다리는 중 bool _isRunning = false; DateTime? _startTime; int _todaySeconds = 0; int _weekSeconds = 0; int _totalSeconds = 0; Timer? _ticker; bool _disposed = false; bool get isLoading => _isLoading; bool get isBusy => _isBusy; bool get isRunning => _isRunning; /// 지금 진행 중인 세션까지 포함한 "오늘" 누적 초. 실시간으로 똑딱거린다. int get todaySecondsLive => _todaySeconds + (_isRunning ? _liveElapsedSeconds : 0); int get weekSecondsLive => _weekSeconds + (_isRunning ? _liveElapsedSeconds : 0); int get totalSecondsLive => _totalSeconds + (_isRunning ? _liveElapsedSeconds : 0); /// 지금 세션 하나만의 경과 시간(타이머 화면 큰 숫자용). int get _liveElapsedSeconds { if (_startTime == null) return 0; final diff = DateTime.now().difference(_startTime!).inSeconds; return diff > 0 ? diff : 0; } int get liveElapsedSeconds => _liveElapsedSeconds; void _safeNotify() { if (!_disposed) notifyListeners(); } Future init() async { await _fetchStatus(); } @override void dispose() { _disposed = true; _ticker?.cancel(); super.dispose(); } Future _fetchStatus() async { try { final response = await http.get( Uri.parse('$baseUrl/api/study/status?studentId=$studentId'), ); if (response.statusCode == 200) { final data = jsonDecode(utf8.decode(response.bodyBytes)); _isRunning = data['isRunning'] == true; _startTime = data['startTime'] != null ? DateTime.tryParse(data['startTime']) : null; _todaySeconds = (data['todaySeconds'] as num?)?.toInt() ?? 0; _weekSeconds = (data['weekSeconds'] as num?)?.toInt() ?? 0; _totalSeconds = (data['totalSeconds'] as num?)?.toInt() ?? 0; _restartTickerIfNeeded(); } } catch (_) { // 조용히 무시 - 마지막으로 알던 상태를 유지한다. } finally { _isLoading = false; _safeNotify(); } } void _restartTickerIfNeeded() { _ticker?.cancel(); if (_isRunning) { _ticker = Timer.periodic( const Duration(seconds: 1), (_) => _safeNotify(), ); } } Future<(bool success, String message)> start() async { _isBusy = true; _safeNotify(); try { final response = await http.post( Uri.parse('$baseUrl/api/study/start'), headers: {"Content-Type": "application/json"}, body: jsonEncode({"studentId": studentId, "studentName": studentName}), ); final result = jsonDecode(utf8.decode(response.bodyBytes)); if (response.statusCode == 200 && result['status'] == 'success') { _isRunning = true; _startTime = DateTime.tryParse(result['startTime']); _restartTickerIfNeeded(); return (true, '공부를 시작했습니다.'); } return (false, '${result['message'] ?? '공부 시작 실패'}'); } catch (e) { return (false, '네트워크 에러: $e'); } finally { _isBusy = false; _safeNotify(); } } Future<(bool success, String message)> stop() async { _isBusy = true; _safeNotify(); try { final response = await http.post( Uri.parse('$baseUrl/api/study/stop'), headers: {"Content-Type": "application/json"}, body: jsonEncode({"studentId": studentId}), ); final result = jsonDecode(utf8.decode(response.bodyBytes)); if (response.statusCode == 200 && result['status'] == 'success') { _isRunning = false; _startTime = null; _ticker?.cancel(); await _fetchStatus(); // 오늘/이번주/전체 합계를 서버 값으로 다시 맞춘다. final minutes = ((result['durationSeconds'] as num?)?.toInt() ?? 0) ~/ 60; return (true, '이번 공부 시간: $minutes분 기록 완료!'); } return (false, '${result['message'] ?? '공부 종료 실패'}'); } catch (e) { return (false, '네트워크 에러: $e'); } finally { _isBusy = false; _safeNotify(); } } }