백품타에 뽀모도로 커스텀 모드 추가
공부/휴식 시간을 5분 단위로 직접 정해두면(기본 50/10), 공부 시간이 끝났을 때 자동으로 일시정지되고 휴식이 끝나면 자동으로 재개된다. 서버의 기존 일시정지/재개 API를 그대로 재사용하는 클라이언트 전용 스케줄러라 백엔드 변경은 없음. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,10 +3,18 @@ import 'dart:async';
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import '../config.dart';
|
import '../config.dart';
|
||||||
|
|
||||||
enum StudyTimerState { idle, running, paused }
|
enum StudyTimerState { idle, running, paused }
|
||||||
|
|
||||||
|
// 🍅 뽀모도로 진행 단계. study/break가 아니면(=null) 뽀모도로가 꺼져있거나 아직 시작 전.
|
||||||
|
enum PomodoroPhase { study, breakTime }
|
||||||
|
|
||||||
|
const String _prefPomodoroEnabled = 'study_pomodoro_enabled';
|
||||||
|
const String _prefPomodoroStudyMinutes = 'study_pomodoro_study_minutes';
|
||||||
|
const String _prefPomodoroBreakMinutes = 'study_pomodoro_break_minutes';
|
||||||
|
|
||||||
class StudyTimerController extends ChangeNotifier {
|
class StudyTimerController extends ChangeNotifier {
|
||||||
final String studentId;
|
final String studentId;
|
||||||
final String studentName;
|
final String studentName;
|
||||||
@@ -24,11 +32,26 @@ class StudyTimerController extends ChangeNotifier {
|
|||||||
Timer? _ticker;
|
Timer? _ticker;
|
||||||
bool _disposed = false;
|
bool _disposed = false;
|
||||||
|
|
||||||
|
bool pomodoroEnabled = false;
|
||||||
|
int studyMinutes = 50;
|
||||||
|
int breakMinutes = 10;
|
||||||
|
PomodoroPhase? _phase;
|
||||||
|
int _phaseSecondsLeft = 0;
|
||||||
|
int completedCycles = 0;
|
||||||
|
bool _phaseTransitioning = false;
|
||||||
|
Timer? _phaseTicker;
|
||||||
|
|
||||||
|
/// 🍅 뽀모도로 진행 상황이 바뀔 때(공부↔휴식 자동 전환) UI에 알림을 띄우기 위한 콜백.
|
||||||
|
/// 자동으로 일어나는 일이라 버튼 액션처럼 (bool,message)를 돌려줄 대상이 없어서 콜백으로 처리한다.
|
||||||
|
void Function(String message)? onPhaseChanged;
|
||||||
|
|
||||||
bool get isLoading => _isLoading;
|
bool get isLoading => _isLoading;
|
||||||
bool get isBusy => _isBusy;
|
bool get isBusy => _isBusy;
|
||||||
bool get isRunning => _state == StudyTimerState.running;
|
bool get isRunning => _state == StudyTimerState.running;
|
||||||
bool get isPaused => _state == StudyTimerState.paused;
|
bool get isPaused => _state == StudyTimerState.paused;
|
||||||
bool get isIdle => _state == StudyTimerState.idle;
|
bool get isIdle => _state == StudyTimerState.idle;
|
||||||
|
PomodoroPhase? get phase => _phase;
|
||||||
|
int get phaseSecondsLeft => _phaseSecondsLeft;
|
||||||
|
|
||||||
/// 지금 세션 하나만의 경과 시간(타이머 화면 큰 숫자용). 일시정지 중엔 멈춰 있다.
|
/// 지금 세션 하나만의 경과 시간(타이머 화면 큰 숫자용). 일시정지 중엔 멈춰 있다.
|
||||||
int get liveElapsedSeconds {
|
int get liveElapsedSeconds {
|
||||||
@@ -51,6 +74,7 @@ class StudyTimerController extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
|
await _loadPomodoroSettings();
|
||||||
await _fetchStatus();
|
await _fetchStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,9 +82,42 @@ class StudyTimerController extends ChangeNotifier {
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
_ticker?.cancel();
|
_ticker?.cancel();
|
||||||
|
_phaseTicker?.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _loadPomodoroSettings() async {
|
||||||
|
try {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
pomodoroEnabled = prefs.getBool(_prefPomodoroEnabled) ?? false;
|
||||||
|
studyMinutes = prefs.getInt(_prefPomodoroStudyMinutes) ?? 50;
|
||||||
|
breakMinutes = prefs.getInt(_prefPomodoroBreakMinutes) ?? 10;
|
||||||
|
} catch (_) {
|
||||||
|
// 조용히 무시 - 기본값(50/10)을 그대로 쓴다.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 뽀모도로 켜짐 여부와 공부/휴식 시간(분)을 저장한다. 타이머가 진행 중일 때 값이
|
||||||
|
/// 흔들리면 안 되므로, 화면에서 idle일 때만 호출하도록 막고 있다.
|
||||||
|
Future<void> setPomodoroSettings({
|
||||||
|
required bool enabled,
|
||||||
|
required int studyMinutes,
|
||||||
|
required int breakMinutes,
|
||||||
|
}) async {
|
||||||
|
pomodoroEnabled = enabled;
|
||||||
|
this.studyMinutes = studyMinutes;
|
||||||
|
this.breakMinutes = breakMinutes;
|
||||||
|
_safeNotify();
|
||||||
|
try {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setBool(_prefPomodoroEnabled, enabled);
|
||||||
|
await prefs.setInt(_prefPomodoroStudyMinutes, studyMinutes);
|
||||||
|
await prefs.setInt(_prefPomodoroBreakMinutes, breakMinutes);
|
||||||
|
} catch (_) {
|
||||||
|
// 조용히 무시 - 이번 세션 동안은 메모리 값으로라도 동작한다.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _fetchStatus() async {
|
Future<void> _fetchStatus() async {
|
||||||
try {
|
try {
|
||||||
final response = await http.get(
|
final response = await http.get(
|
||||||
@@ -108,7 +165,7 @@ class StudyTimerController extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<(bool success, String message)> start() async {
|
Future<(bool success, String message)> start() async {
|
||||||
return _action(
|
final result = await _action(
|
||||||
'/api/study/start',
|
'/api/study/start',
|
||||||
body: {"studentId": studentId, "studentName": studentName},
|
body: {"studentId": studentId, "studentName": studentName},
|
||||||
onSuccess: (_) async {
|
onSuccess: (_) async {
|
||||||
@@ -116,6 +173,14 @@ class StudyTimerController extends ChangeNotifier {
|
|||||||
return '공부를 시작했습니다.';
|
return '공부를 시작했습니다.';
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
if (result.$1 && pomodoroEnabled) {
|
||||||
|
completedCycles = 0;
|
||||||
|
_phase = PomodoroPhase.study;
|
||||||
|
_phaseSecondsLeft = studyMinutes * 60;
|
||||||
|
_startPhaseTicker();
|
||||||
|
_safeNotify();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<(bool success, String message)> pause() async {
|
Future<(bool success, String message)> pause() async {
|
||||||
@@ -139,7 +204,7 @@ class StudyTimerController extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<(bool success, String message)> stop() async {
|
Future<(bool success, String message)> stop() async {
|
||||||
return _action(
|
final result = await _action(
|
||||||
'/api/study/stop',
|
'/api/study/stop',
|
||||||
onSuccess: (result) async {
|
onSuccess: (result) async {
|
||||||
await _fetchStatus(); // 오늘/이번주/전체 합계를 서버 값으로 다시 맞춘다.
|
await _fetchStatus(); // 오늘/이번주/전체 합계를 서버 값으로 다시 맞춘다.
|
||||||
@@ -148,6 +213,56 @@ class StudyTimerController extends ChangeNotifier {
|
|||||||
return '이번 공부 시간: $minutes분 기록 완료!';
|
return '이번 공부 시간: $minutes분 기록 완료!';
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
if (result.$1) _stopPhaseTicker();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _stopPhaseTicker() {
|
||||||
|
_phaseTicker?.cancel();
|
||||||
|
_phaseTicker = null;
|
||||||
|
_phase = null;
|
||||||
|
_phaseSecondsLeft = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 🍅 1초마다 뽀모도로 남은 시간을 줄이고, 0이 되면 공부↔휴식을 자동으로 전환한다.
|
||||||
|
/// 휴식 구간에서도 계속 흘러야 해서, running일 때만 도는 _ticker와는 별도로 관리한다.
|
||||||
|
void _startPhaseTicker() {
|
||||||
|
_phaseTicker?.cancel();
|
||||||
|
_phaseTicker = Timer.periodic(const Duration(seconds: 1), (_) {
|
||||||
|
if (_phase == null || _phaseTransitioning) return;
|
||||||
|
_phaseSecondsLeft -= 1;
|
||||||
|
if (_phaseSecondsLeft <= 0) {
|
||||||
|
_advancePhase();
|
||||||
|
} else {
|
||||||
|
_safeNotify();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _advancePhase() async {
|
||||||
|
_phaseTransitioning = true;
|
||||||
|
if (_phase == PomodoroPhase.study) {
|
||||||
|
final (success, _) = await pause();
|
||||||
|
if (success) {
|
||||||
|
completedCycles += 1;
|
||||||
|
_phase = PomodoroPhase.breakTime;
|
||||||
|
_phaseSecondsLeft = breakMinutes * 60;
|
||||||
|
onPhaseChanged?.call('공부 끝! $breakMinutes분간 쉬어가요.');
|
||||||
|
} else {
|
||||||
|
_phaseSecondsLeft = 1; // 네트워크 문제 등으로 실패했으면 잠시 후 다시 시도.
|
||||||
|
}
|
||||||
|
} else if (_phase == PomodoroPhase.breakTime) {
|
||||||
|
final (success, _) = await resume();
|
||||||
|
if (success) {
|
||||||
|
_phase = PomodoroPhase.study;
|
||||||
|
_phaseSecondsLeft = studyMinutes * 60;
|
||||||
|
onPhaseChanged?.call('휴식 끝! 다시 공부를 시작해요.');
|
||||||
|
} else {
|
||||||
|
_phaseSecondsLeft = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_phaseTransitioning = false;
|
||||||
|
_safeNotify();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<(bool success, String message)> _action(
|
Future<(bool success, String message)> _action(
|
||||||
|
|||||||
@@ -34,9 +34,15 @@ class _StudyTimerScreenState extends State<StudyTimerScreen> {
|
|||||||
studentId: widget.studentId,
|
studentId: widget.studentId,
|
||||||
studentName: widget.studentName,
|
studentName: widget.studentName,
|
||||||
);
|
);
|
||||||
|
_controller.onPhaseChanged = _handlePhaseChanged;
|
||||||
_controller.init();
|
_controller.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _handlePhaseChanged(String message) {
|
||||||
|
if (!mounted) return;
|
||||||
|
AppNotice.show(context, message, icon: Icons.timer_rounded);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_controller.dispose();
|
_controller.dispose();
|
||||||
@@ -53,6 +59,24 @@ class _StudyTimerScreenState extends State<StudyTimerScreen> {
|
|||||||
return '$hh:$mm:$ss';
|
return '$hh:$mm:$ss';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _formatMinSec(int totalSeconds) {
|
||||||
|
final s = totalSeconds < 0 ? 0 : totalSeconds;
|
||||||
|
final m = s ~/ 60;
|
||||||
|
final sec = s % 60;
|
||||||
|
return '$m:${sec.toString().padLeft(2, '0')}';
|
||||||
|
}
|
||||||
|
|
||||||
|
String _statusLabel(bool isRunning, bool isPaused) {
|
||||||
|
if (_controller.pomodoroEnabled && _controller.phase != null) {
|
||||||
|
return _controller.phase == PomodoroPhase.study
|
||||||
|
? '뽀모도로 - 공부 중이에요'
|
||||||
|
: '뽀모도로 - 쉬는 중이에요';
|
||||||
|
}
|
||||||
|
if (isRunning) return '지금 공부 중이에요';
|
||||||
|
if (isPaused) return '일시정지 중이에요';
|
||||||
|
return '공부를 시작해볼까요?';
|
||||||
|
}
|
||||||
|
|
||||||
String _formatHM(int totalSeconds) {
|
String _formatHM(int totalSeconds) {
|
||||||
final h = totalSeconds ~/ 3600;
|
final h = totalSeconds ~/ 3600;
|
||||||
final m = (totalSeconds % 3600) ~/ 60;
|
final m = (totalSeconds % 3600) ~/ 60;
|
||||||
@@ -72,6 +96,146 @@ class _StudyTimerScreenState extends State<StudyTimerScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _showPomodoroSettings() async {
|
||||||
|
bool enabled = _controller.pomodoroEnabled;
|
||||||
|
int studyMinutes = _controller.studyMinutes;
|
||||||
|
int breakMinutes = _controller.breakMinutes;
|
||||||
|
final bool canEdit = _controller.isIdle;
|
||||||
|
|
||||||
|
await showModalBottomSheet<void>(
|
||||||
|
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: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Expanded(
|
||||||
|
child: Text(
|
||||||
|
'뽀모도로 모드',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Switch(
|
||||||
|
value: enabled,
|
||||||
|
activeThumbColor: AppPalette.ink,
|
||||||
|
onChanged: canEdit
|
||||||
|
? (v) => setSheetState(() => enabled = v)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
'공부/휴식 시간을 정해두면, 공부 시간이 끝났을 때 자동으로\n일시정지되고 휴식이 끝나면 자동으로 다시 시작해요.',
|
||||||
|
style: TextStyle(color: Colors.grey, fontSize: 12.5),
|
||||||
|
),
|
||||||
|
if (!canEdit) ...[
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
const Text(
|
||||||
|
'타이머가 멈춰있을 때만 설정을 바꿀 수 있어요.',
|
||||||
|
style: TextStyle(color: Colors.redAccent, fontSize: 12.5),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
const SizedBox(height: 18),
|
||||||
|
_minuteStepper(
|
||||||
|
label: '공부 시간',
|
||||||
|
minutes: studyMinutes,
|
||||||
|
enabled: canEdit,
|
||||||
|
onChanged: (v) => setSheetState(() => studyMinutes = v),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
_minuteStepper(
|
||||||
|
label: '휴식 시간',
|
||||||
|
minutes: breakMinutes,
|
||||||
|
enabled: canEdit,
|
||||||
|
onChanged: (v) => setSheetState(() => breakMinutes = v),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 18),
|
||||||
|
ElevatedButton(
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: AppPalette.ink,
|
||||||
|
foregroundColor: AppPalette.paper,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||||
|
),
|
||||||
|
onPressed: !canEdit
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
await _controller.setPomodoroSettings(
|
||||||
|
enabled: enabled,
|
||||||
|
studyMinutes: studyMinutes,
|
||||||
|
breakMinutes: breakMinutes,
|
||||||
|
);
|
||||||
|
if (sheetContext.mounted) {
|
||||||
|
Navigator.of(sheetContext).pop();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text('저장'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _minuteStepper({
|
||||||
|
required String label,
|
||||||
|
required int minutes,
|
||||||
|
required bool enabled,
|
||||||
|
required ValueChanged<int> onChanged,
|
||||||
|
}) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.remove_circle_outline_rounded),
|
||||||
|
onPressed: enabled && 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: enabled && minutes < 120
|
||||||
|
? () => onChanged(minutes + 5)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void _openRanking() {
|
void _openRanking() {
|
||||||
if (widget.grade == null) {
|
if (widget.grade == null) {
|
||||||
AppNotice.show(context, '학년 정보가 없어 랭킹을 볼 수 없어요.');
|
AppNotice.show(context, '학년 정보가 없어 랭킹을 볼 수 없어요.');
|
||||||
@@ -102,6 +266,11 @@ class _StudyTimerScreenState extends State<StudyTimerScreen> {
|
|||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
title: const TitlePill('백품타'),
|
title: const TitlePill('백품타'),
|
||||||
actions: [
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.tune_rounded),
|
||||||
|
tooltip: '뽀모도로 설정',
|
||||||
|
onPressed: _showPomodoroSettings,
|
||||||
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.leaderboard_rounded),
|
icon: const Icon(Icons.leaderboard_rounded),
|
||||||
tooltip: '학년 랭킹',
|
tooltip: '학년 랭킹',
|
||||||
@@ -118,16 +287,26 @@ class _StudyTimerScreenState extends State<StudyTimerScreen> {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
isRunning
|
_statusLabel(isRunning, isPaused),
|
||||||
? '지금 공부 중이에요'
|
|
||||||
: isPaused
|
|
||||||
? '일시정지 중이에요'
|
|
||||||
: '공부를 시작해볼까요?',
|
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
color: Colors.grey,
|
color: Colors.grey,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (_controller.pomodoroEnabled &&
|
||||||
|
_controller.phase != null) ...[
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'${_controller.phase == PomodoroPhase.study ? '다음 휴식까지' : '다음 공부까지'} '
|
||||||
|
'${_formatMinSec(_controller.phaseSecondsLeft)} · '
|
||||||
|
'완료 ${_controller.completedCycles}회',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12.5,
|
||||||
|
color: Colors.grey,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Text(
|
Text(
|
||||||
_formatHMS(_controller.liveElapsedSeconds),
|
_formatHMS(_controller.liveElapsedSeconds),
|
||||||
@@ -173,11 +352,21 @@ class _StudyTimerScreenState extends State<StudyTimerScreen> {
|
|||||||
return _circleButton(
|
return _circleButton(
|
||||||
size: 140,
|
size: 140,
|
||||||
icon: Icons.play_arrow_rounded,
|
icon: Icons.play_arrow_rounded,
|
||||||
label: '공부 시작',
|
label: _controller.pomodoroEnabled ? '뽀모도로 시작' : '공부 시작',
|
||||||
color: AppPalette.ink,
|
color: AppPalette.ink,
|
||||||
onTap: () => _runAction(_controller.start),
|
onTap: () => _runAction(_controller.start),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (_controller.pomodoroEnabled && _controller.phase != null) {
|
||||||
|
// 뽀모도로 자동 전환 중에는 일시정지/재개는 자동으로 일어나니 종료 버튼만 보여준다.
|
||||||
|
return _circleButton(
|
||||||
|
size: 140,
|
||||||
|
icon: Icons.stop_rounded,
|
||||||
|
label: '공부 종료',
|
||||||
|
color: Colors.red[400]!,
|
||||||
|
onTap: () => _runAction(_controller.stop),
|
||||||
|
);
|
||||||
|
}
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
Reference in New Issue
Block a user