diff --git a/src/main/kotlin/com/github/akshayashokcode/devfocus/services/pomodoro/PomodoroTimerService.kt b/src/main/kotlin/com/github/akshayashokcode/devfocus/services/pomodoro/PomodoroTimerService.kt index ba2fb4c..4ff1091 100644 --- a/src/main/kotlin/com/github/akshayashokcode/devfocus/services/pomodoro/PomodoroTimerService.kt +++ b/src/main/kotlin/com/github/akshayashokcode/devfocus/services/pomodoro/PomodoroTimerService.kt @@ -2,7 +2,10 @@ package com.github.akshayashokcode.devfocus.services.pomodoro import com.github.akshayashokcode.devfocus.model.PomodoroMode import com.github.akshayashokcode.devfocus.model.PomodoroSettings +import com.intellij.notification.NotificationGroupManager +import com.intellij.notification.NotificationType import com.intellij.openapi.components.Service +import com.intellij.openapi.project.Project import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -15,17 +18,20 @@ import kotlinx.coroutines.launch import java.util.concurrent.TimeUnit @Service(Service.Level.PROJECT) -class PomodoroTimerService { +class PomodoroTimerService(private val project: Project) { companion object { private const val ONE_SECOND = 1000L + private const val NOTIFICATION_GROUP_ID = "DevFocus Notifications" } enum class TimerState { IDLE, RUNNING, PAUSED } + enum class TimerPhase { WORK, BREAK } private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) private var job: Job? = null private var settings = PomodoroMode.CLASSIC.toSettings() + private var currentPhase = TimerPhase.WORK private var remainingTimeMs: Long = TimeUnit.MINUTES.toMillis(settings.sessionMinutes.toLong()) private val _timeLeft = MutableStateFlow(formatTime(remainingTimeMs)) @@ -55,10 +61,73 @@ class PomodoroTimerService { } if (remainingTimeMs <= 0) { _state.value = TimerState.IDLE + onSessionComplete() } } } + private fun onSessionComplete() { + val currentSessionNum = _currentSession.value + val totalSessions = settings.sessionsPerRound + + if (currentPhase == TimerPhase.WORK) { + // Work session complete + if (currentSessionNum >= totalSessions) { + // Last session complete - all done! + NotificationGroupManager.getInstance() + .getNotificationGroup(NOTIFICATION_GROUP_ID) + .createNotification( + "\uD83C\uDF89 All Sessions Complete!", + "You've completed all $totalSessions sessions. Take a well-deserved break!", + NotificationType.INFORMATION + ) + .notify(project) + + // Reset to initial state + currentPhase = TimerPhase.WORK + _currentSession.value = 1 + remainingTimeMs = TimeUnit.MINUTES.toMillis(settings.sessionMinutes.toLong()) + _timeLeft.value = formatTime(remainingTimeMs) + } else { + // Work session complete - start break + NotificationGroupManager.getInstance() + .getNotificationGroup(NOTIFICATION_GROUP_ID) + .createNotification( + "\uD83C\uDF45 Session Complete!", + "Great work! Starting ${settings.breakMinutes}-minute break ☕.", + NotificationType.INFORMATION + ) + .notify(project) + + // Start break timer + currentPhase = TimerPhase.BREAK + remainingTimeMs = TimeUnit.MINUTES.toMillis(settings.breakMinutes.toLong()) + _timeLeft.value = formatTime(remainingTimeMs) + start() + } + + } else { + // Break complete + // More sessions remaining - start next session + NotificationGroupManager.getInstance() + .getNotificationGroup(NOTIFICATION_GROUP_ID) + .createNotification( + "☕ Break Complete!", + "Starting session ${currentSessionNum + 1} of $totalSessions.", + NotificationType.INFORMATION + ) + .notify(project) + + // Start next work session + currentPhase = TimerPhase.WORK + _currentSession.value = currentSessionNum + 1 + remainingTimeMs = TimeUnit.MINUTES.toMillis(settings.sessionMinutes.toLong()) + _timeLeft.value = formatTime(remainingTimeMs) + start() + + } + } + fun pause() { if (_state.value == TimerState.RUNNING) { job?.cancel() @@ -72,6 +141,7 @@ class PomodoroTimerService { job = null // Reset to initial state + currentPhase = TimerPhase.WORK remainingTimeMs = TimeUnit.MINUTES.toMillis(settings.sessionMinutes.toLong()) _timeLeft.value = formatTime(remainingTimeMs) _currentSession.value = 1 @@ -92,6 +162,7 @@ class PomodoroTimerService { settings = newSettings _settings.value = newSettings + currentPhase = TimerPhase.WORK remainingTimeMs = TimeUnit.MINUTES.toMillis(newSettings.sessionMinutes.toLong()) _timeLeft.value = formatTime(remainingTimeMs) _currentSession.value = 1 @@ -105,7 +176,11 @@ class PomodoroTimerService { fun getSettings(): PomodoroSettings = settings fun getProgress(): Float { - val totalMs = TimeUnit.MINUTES.toMillis(settings.sessionMinutes.toLong()) + val totalMs = if (currentPhase == TimerPhase.WORK) { + TimeUnit.MINUTES.toMillis(settings.sessionMinutes.toLong()) + } else { + TimeUnit.MINUTES.toMillis(settings.breakMinutes.toLong()) + } return if (totalMs > 0) remainingTimeMs.toFloat() / totalMs.toFloat() else 0f } } \ No newline at end of file diff --git a/src/main/kotlin/com/github/akshayashokcode/devfocus/ui/components/CircularTimerPanel.kt b/src/main/kotlin/com/github/akshayashokcode/devfocus/ui/components/CircularTimerPanel.kt index 1a6a0e4..2bd455f 100644 --- a/src/main/kotlin/com/github/akshayashokcode/devfocus/ui/components/CircularTimerPanel.kt +++ b/src/main/kotlin/com/github/akshayashokcode/devfocus/ui/components/CircularTimerPanel.kt @@ -2,6 +2,7 @@ package com.github.akshayashokcode.devfocus.ui.components import java.awt.* import javax.swing.JPanel +import javax.swing.UIManager class CircularTimerPanel : JPanel() { @@ -13,7 +14,6 @@ class CircularTimerPanel : JPanel() { private val workColor = Color(74, 144, 226) // Blue for focus/work private val breakColor = Color(80, 200, 120) // Green for rest private val backgroundColor = Color(224, 224, 224) // Light gray - private val textColor = Color(60, 60, 60) // Dark gray for text private val diameter = 180 private val strokeWidth = 10f @@ -34,7 +34,7 @@ class CircularTimerPanel : JPanel() { super.paintComponent(g) val g2d = g as Graphics2D - // Enable anti-aliasing for smooth circles + // Enable antialiasing for smooth circles g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON) @@ -60,7 +60,7 @@ class CircularTimerPanel : JPanel() { g2d.drawArc(arcX, arcY, arcSize, arcSize, 90, -arcAngle) // Negative for clockwise // Draw time text in center - g2d.color = textColor + g2d.color = UIManager.getColor("Label.foreground") ?: Color.BLACK val font = Font("SansSerif", Font.BOLD, 36) g2d.font = font diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 251881d..c41685c 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -33,5 +33,6 @@ /> +