Skip to content

Commit dc4e5ad

Browse files
Merge pull request #61 from AkshayAshokCode/#37
#37 Replace Current Timer UI with CircularTimerPanel
2 parents ae46b1b + 75d6044 commit dc4e5ad

File tree

7 files changed

+317
-32
lines changed

7 files changed

+317
-32
lines changed

src/main/kotlin/com/github/akshayashokcode/devfocus/model/PomodoroMode.kt

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,46 @@ enum class PomodoroMode(
55
val emoji: String,
66
val sessionMinutes: Int,
77
val breakMinutes: Int,
8-
val sessionsPerRound: Int
8+
val sessionsPerRound: Int,
9+
val longBreakMinutes: Int,
10+
val longBreakAfter: Int
911
) {
1012
CLASSIC(
1113
displayName = "Classic Pomodoro",
1214
emoji = "🍅",
1315
sessionMinutes = 25,
1416
breakMinutes = 5,
15-
sessionsPerRound = 4
17+
sessionsPerRound = 4,
18+
longBreakMinutes = 15,
19+
longBreakAfter = 4
1620
),
1721
DEEP_WORK(
1822
displayName = "Deep Work",
1923
emoji = "",
20-
sessionMinutes = 52,
21-
breakMinutes = 17,
22-
sessionsPerRound = 3
24+
sessionMinutes = 50,
25+
breakMinutes = 10,
26+
sessionsPerRound = 2,
27+
longBreakMinutes = 30,
28+
longBreakAfter = 2
2329
),
2430
CUSTOM(
2531
displayName = "Custom",
2632
emoji = "⚙️",
2733
sessionMinutes = 25,
2834
breakMinutes = 5,
29-
sessionsPerRound = 4
35+
sessionsPerRound = 4,
36+
longBreakMinutes = 15,
37+
longBreakAfter = 4
3038
);
3139

3240
fun toSettings(): PomodoroSettings {
3341
return PomodoroSettings(
3442
mode = this,
3543
sessionMinutes = sessionMinutes,
3644
breakMinutes = breakMinutes,
37-
sessionsPerRound = sessionsPerRound
45+
sessionsPerRound = sessionsPerRound,
46+
longBreakMinutes = longBreakMinutes,
47+
longBreakAfter = longBreakAfter
3848
)
3949
}
4050

src/main/kotlin/com/github/akshayashokcode/devfocus/model/PomodoroSettings.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,7 @@ data class PomodoroSettings(
44
val mode: PomodoroMode = PomodoroMode.CLASSIC,
55
val sessionMinutes: Int,
66
val breakMinutes: Int,
7-
val sessionsPerRound: Int
7+
val sessionsPerRound: Int,
8+
val longBreakMinutes: Int = breakMinutes,
9+
val longBreakAfter: Int = sessionsPerRound
810
)

src/main/kotlin/com/github/akshayashokcode/devfocus/services/pomodoro/PomodoroTimerService.kt

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.github.akshayashokcode.devfocus.services.pomodoro
22

3+
import com.github.akshayashokcode.devfocus.model.PomodoroMode
34
import com.github.akshayashokcode.devfocus.model.PomodoroSettings
45
import com.intellij.openapi.components.Service
56
import kotlinx.coroutines.CoroutineScope
@@ -16,28 +17,35 @@ import java.util.concurrent.TimeUnit
1617
@Service(Service.Level.PROJECT)
1718
class PomodoroTimerService {
1819
companion object {
19-
private const val DEFAULT_MINUTES = 25
2020
private const val ONE_SECOND = 1000L
2121
}
2222

2323
enum class TimerState { IDLE, RUNNING, PAUSED }
2424

2525
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
26-
27-
private var remainingTimeMs: Long = TimeUnit.MINUTES.toMillis(DEFAULT_MINUTES.toLong())
2826
private var job: Job? = null
2927

28+
private var settings = PomodoroMode.CLASSIC.toSettings()
29+
private var remainingTimeMs: Long = TimeUnit.MINUTES.toMillis(settings.sessionMinutes.toLong())
30+
3031
private val _timeLeft = MutableStateFlow(formatTime(remainingTimeMs))
3132
val timeLeft: StateFlow<String> = _timeLeft
3233

3334
private val _state = MutableStateFlow(TimerState.IDLE)
3435
val state: StateFlow<TimerState> = _state
3536

36-
private var settings = PomodoroSettings(25, 5, 4) // default
37+
private val _currentSession = MutableStateFlow(1)
38+
val currentSession: StateFlow<Int> = _currentSession
39+
40+
private val _settings = MutableStateFlow(settings)
41+
val settingsFlow: StateFlow<PomodoroSettings> = _settings
3742

3843
fun start() {
3944
if (_state.value == TimerState.RUNNING) return
4045

46+
// Cancel any existing job to ensure only one timer is running
47+
job?.cancel()
48+
4149
_state.value = TimerState.RUNNING
4250
job = coroutineScope.launch {
4351
while (remainingTimeMs > 0 && isActive) {
@@ -59,9 +67,14 @@ class PomodoroTimerService {
5967
}
6068

6169
fun reset() {
70+
// Cancel any running job
6271
job?.cancel()
63-
remainingTimeMs = TimeUnit.MINUTES.toMillis(DEFAULT_MINUTES.toLong())
72+
job = null
73+
74+
// Reset to initial state
75+
remainingTimeMs = TimeUnit.MINUTES.toMillis(settings.sessionMinutes.toLong())
6476
_timeLeft.value = formatTime(remainingTimeMs)
77+
_currentSession.value = 1
6578
_state.value = TimerState.IDLE
6679
}
6780

@@ -72,10 +85,27 @@ class PomodoroTimerService {
7285
return String.format("%02d:%02d", minutes, seconds)
7386
}
7487

75-
fun applySettings(settings: PomodoroSettings) {
76-
this.settings = settings
77-
remainingTimeMs = TimeUnit.MINUTES.toMillis(settings.sessionMinutes.toLong())
88+
fun applySettings(newSettings: PomodoroSettings) {
89+
// Cancel any running job when settings change
90+
job?.cancel()
91+
job = null
92+
93+
settings = newSettings
94+
_settings.value = newSettings
95+
remainingTimeMs = TimeUnit.MINUTES.toMillis(newSettings.sessionMinutes.toLong())
7896
_timeLeft.value = formatTime(remainingTimeMs)
97+
_currentSession.value = 1
7998
_state.value = TimerState.IDLE
8099
}
100+
101+
fun applyMode(mode: PomodoroMode) {
102+
applySettings(mode.toSettings())
103+
}
104+
105+
fun getSettings(): PomodoroSettings = settings
106+
107+
fun getProgress(): Float {
108+
val totalMs = TimeUnit.MINUTES.toMillis(settings.sessionMinutes.toLong())
109+
return if (totalMs > 0) remainingTimeMs.toFloat() / totalMs.toFloat() else 0f
110+
}
81111
}
Lines changed: 104 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,145 @@
11
package com.github.akshayashokcode.devfocus.toolWindow
22

3+
import com.github.akshayashokcode.devfocus.model.PomodoroMode
34
import com.github.akshayashokcode.devfocus.model.PomodoroSettings
45
import com.github.akshayashokcode.devfocus.services.pomodoro.PomodoroTimerService
6+
import com.github.akshayashokcode.devfocus.ui.components.CircularTimerPanel
7+
import com.github.akshayashokcode.devfocus.ui.components.SessionIndicatorPanel
58
import com.github.akshayashokcode.devfocus.ui.settings.PomodoroSettingsPanel
69
import com.intellij.openapi.project.Project
710
import com.intellij.ui.components.JBPanel
811
import kotlinx.coroutines.*
912
import kotlinx.coroutines.flow.collectLatest
10-
import java.awt.BorderLayout
11-
import java.awt.FlowLayout
13+
import java.awt.*
1214
import javax.swing.*
1315

1416
class PomodoroToolWindowPanel(private val project: Project) : JBPanel<JBPanel<*>>(BorderLayout()) {
1517

1618
private val timerService = project.getService(PomodoroTimerService::class.java) ?: error("PomodoroTimerService not available")
1719

18-
private val timeLabel = JLabel("25:00").apply {
20+
// Mode selector
21+
private val modeComboBox = JComboBox(PomodoroMode.entries.toTypedArray()).apply {
22+
selectedItem = PomodoroMode.CLASSIC
23+
}
24+
25+
// Info label showing current mode settings
26+
private val infoLabel = JLabel("📊 25 min work • 5 min break").apply {
1927
horizontalAlignment = SwingConstants.CENTER
20-
font = font.deriveFont(32f)
28+
font = font.deriveFont(Font.PLAIN, 12f)
2129
}
2230

31+
// Circular timer display
32+
private val circularTimer = CircularTimerPanel()
33+
34+
// Session indicator with tomato icons
35+
private val sessionIndicator = SessionIndicatorPanel()
36+
37+
// Control buttons
2338
private val startButton = JButton("Start")
2439
private val pauseButton = JButton("Pause")
2540
private val resetButton = JButton("Reset")
2641

42+
// Custom settings panel (only visible when Custom mode selected)
2743
private val settingsPanel = PomodoroSettingsPanel { session, breakTime, sessions ->
28-
timerService.applySettings(PomodoroSettings(session, breakTime, sessions))
44+
timerService.applySettings(PomodoroSettings(PomodoroMode.CUSTOM, session, breakTime, sessions))
45+
updateInfoLabel(session, breakTime)
46+
updateProgressBar(sessions)
2947
}
3048

3149
private val scope = CoroutineScope(Dispatchers.Default)
3250
private var stateJob: Job? = null
3351
private var timeJob: Job? = null
52+
private var sessionJob: Job? = null
3453

3554
init {
36-
val buttonPanel = JPanel(FlowLayout()).apply {
55+
buildUI()
56+
setupListeners()
57+
observeTimer()
58+
updateSettingsPanelVisibility()
59+
}
60+
61+
private fun buildUI() {
62+
// Top panel with mode selector
63+
val topPanel = JPanel(BorderLayout(5, 5)).apply {
64+
border = BorderFactory.createEmptyBorder(10, 10, 5, 10)
65+
add(modeComboBox, BorderLayout.CENTER)
66+
}
67+
68+
// Info panel
69+
val infoPanel = JPanel(FlowLayout(FlowLayout.CENTER)).apply {
70+
add(infoLabel)
71+
}
72+
73+
// Timer panel
74+
val timerPanel = JPanel(BorderLayout()).apply {
75+
border = BorderFactory.createEmptyBorder(20, 10, 20, 10)
76+
add(circularTimer, BorderLayout.CENTER)
77+
}
78+
79+
// Progress panel
80+
val progressPanel = JPanel(BorderLayout(5, 5)).apply {
81+
border = BorderFactory.createEmptyBorder(0, 20, 10, 20)
82+
add(sessionIndicator, BorderLayout.CENTER)
83+
}
84+
85+
// Button panel
86+
val buttonPanel = JPanel(FlowLayout(FlowLayout.CENTER, 10, 5)).apply {
3787
add(startButton)
3888
add(pauseButton)
3989
add(resetButton)
4090
}
4191

42-
val centerPanel = JPanel(BorderLayout()).apply {
43-
add(timeLabel, BorderLayout.CENTER)
44-
add(buttonPanel, BorderLayout.SOUTH)
92+
// Center content
93+
val centerPanel = JPanel().apply {
94+
layout = BoxLayout(this, BoxLayout.Y_AXIS)
95+
add(infoPanel)
96+
add(timerPanel)
97+
add(progressPanel)
98+
add(buttonPanel)
4599
}
46100

101+
add(topPanel, BorderLayout.NORTH)
47102
add(centerPanel, BorderLayout.CENTER)
48103
add(settingsPanel, BorderLayout.SOUTH)
49-
50-
setupListeners()
51-
observeTimer()
52104
}
53105

54106
private fun setupListeners() {
55107
startButton.addActionListener { timerService.start() }
56108
pauseButton.addActionListener { timerService.pause() }
57109
resetButton.addActionListener { timerService.reset() }
110+
111+
modeComboBox.addActionListener {
112+
val selectedMode = modeComboBox.selectedItem as PomodoroMode
113+
if (selectedMode != PomodoroMode.CUSTOM) {
114+
timerService.applyMode(selectedMode)
115+
updateInfoLabel(selectedMode.sessionMinutes, selectedMode.breakMinutes)
116+
updateProgressBar(selectedMode.sessionsPerRound)
117+
}
118+
updateSettingsPanelVisibility()
119+
}
120+
}
121+
122+
private fun updateSettingsPanelVisibility() {
123+
val isCustom = modeComboBox.selectedItem == PomodoroMode.CUSTOM
124+
settingsPanel.isVisible = isCustom
125+
revalidate()
126+
repaint()
127+
}
128+
129+
private fun updateInfoLabel(sessionMin: Int, breakMin: Int) {
130+
infoLabel.text = "📊 $sessionMin min work • $breakMin min break"
131+
}
132+
133+
private fun updateProgressBar(totalSessions: Int) {
134+
sessionIndicator.updateSessions(timerService.currentSession.value, totalSessions)
58135
}
59136

60137
private fun observeTimer() {
61138
timeJob = scope.launch {
62-
timerService.timeLeft.collectLatest {
139+
timerService.timeLeft.collectLatest { time ->
63140
SwingUtilities.invokeLater {
64-
timeLabel.text = it
141+
val progress = timerService.getProgress()
142+
circularTimer.updateTimer(time, progress, false)
65143
}
66144
}
67145
}
@@ -72,6 +150,17 @@ class PomodoroToolWindowPanel(private val project: Project) : JBPanel<JBPanel<*>
72150
startButton.isEnabled = it != PomodoroTimerService.TimerState.RUNNING
73151
pauseButton.isEnabled = it == PomodoroTimerService.TimerState.RUNNING
74152
resetButton.isEnabled = it != PomodoroTimerService.TimerState.IDLE
153+
// Disable mode selector when timer is active (running or paused)
154+
modeComboBox.isEnabled = it == PomodoroTimerService.TimerState.IDLE
155+
}
156+
}
157+
}
158+
159+
sessionJob = scope.launch {
160+
timerService.currentSession.collectLatest { session ->
161+
SwingUtilities.invokeLater {
162+
val settings = timerService.getSettings()
163+
sessionIndicator.updateSessions(session, settings.sessionsPerRound)
75164
}
76165
}
77166
}
@@ -80,6 +169,7 @@ class PomodoroToolWindowPanel(private val project: Project) : JBPanel<JBPanel<*>
80169
fun dispose() {
81170
stateJob?.cancel()
82171
timeJob?.cancel()
172+
sessionJob?.cancel()
83173
scope.cancel()
84174
}
85175
}

0 commit comments

Comments
 (0)