From 39056649f7448f0069f199baa42a531a801e4fd0 Mon Sep 17 00:00:00 2001 From: akshay ashok Date: Sat, 21 Feb 2026 11:44:43 +0530 Subject: [PATCH 1/5] #15 Polish UI --- .../toolWindow/PomodoroToolWindowPanel.kt | 62 ++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/com/github/akshayashokcode/devfocus/toolWindow/PomodoroToolWindowPanel.kt b/src/main/kotlin/com/github/akshayashokcode/devfocus/toolWindow/PomodoroToolWindowPanel.kt index 29c734c..5d3a6bb 100644 --- a/src/main/kotlin/com/github/akshayashokcode/devfocus/toolWindow/PomodoroToolWindowPanel.kt +++ b/src/main/kotlin/com/github/akshayashokcode/devfocus/toolWindow/PomodoroToolWindowPanel.kt @@ -10,13 +10,20 @@ import com.intellij.openapi.project.Project import com.intellij.ui.components.JBPanel import kotlinx.coroutines.* import kotlinx.coroutines.flow.collectLatest -import java.awt.* +import java.awt.BorderLayout +import java.awt.FlowLayout +import java.awt.Font +import java.awt.event.ComponentAdapter +import java.awt.event.ComponentEvent import javax.swing.* class PomodoroToolWindowPanel(private val project: Project) : JBPanel>(BorderLayout()) { private val timerService = project.getService(PomodoroTimerService::class.java) ?: error("PomodoroTimerService not available") + // Layout orientation tracking + private var isHorizontalLayout = false + // Mode selector private val modeComboBox = JComboBox(PomodoroMode.entries.toTypedArray()).apply { selectedItem = PomodoroMode.CLASSIC @@ -56,9 +63,18 @@ class PomodoroToolWindowPanel(private val project: Project) : JBPanel setupListeners() observeTimer() updateSettingsPanelVisibility() + setupLayoutListener() } private fun buildUI() { + if(isHorizontalLayout) { + buildHorizontalLayout() + } else { + buildVerticalLayout() + } + } + + private fun buildVerticalLayout() { // Top panel with mode selector val topPanel = JPanel(BorderLayout(5, 5)).apply { border = BorderFactory.createEmptyBorder(10, 10, 5, 10) @@ -103,6 +119,10 @@ class PomodoroToolWindowPanel(private val project: Project) : JBPanel add(settingsPanel, BorderLayout.SOUTH) } + private fun buildHorizontalLayout() { + buildVerticalLayout() + } + private fun setupListeners() { startButton.addActionListener { timerService.start() } pauseButton.addActionListener { timerService.pause() } @@ -134,6 +154,46 @@ class PomodoroToolWindowPanel(private val project: Project) : JBPanel sessionIndicator.updateSessions(timerService.currentSession.value, totalSessions) } + private fun setupLayoutListener() { + addComponentListener(object : ComponentAdapter() { + override fun componentResized(e: ComponentEvent?) { + checkAndUpdateLayout() + } + }) + } + + private fun checkAndUpdateLayout() { + val width = width + val height = height + + // Determine if we should use horizontal layout (width > height * 1.5) + val shouldBeHorizontal = width > height * 1.5 + + // Only rebuild if layout orientation changed + if (shouldBeHorizontal != isHorizontalLayout) { + isHorizontalLayout = shouldBeHorizontal + rebuildLayout() + } + } + + private fun rebuildLayout() { + // Remove all components + removeAll() + + // Rebuild UI with new layout + buildUI() + + // Reconnect listeners (buttons are recreated, need new listeners) + setupListeners() + + // Update setting panel visibility + updateSettingsPanelVisibility() + + // Refresh the panel + revalidate() + repaint() + } + private fun observeTimer() { timeJob = scope.launch { timerService.timeLeft.collectLatest { time -> From c2d0ec3969bdeff639972de6892d47463d80ca42 Mon Sep 17 00:00:00 2001 From: akshay ashok Date: Sat, 21 Feb 2026 16:23:56 +0530 Subject: [PATCH 2/5] #15 Polished UI --- .../toolWindow/PomodoroToolWindowPanel.kt | 49 ++++++++++++++++--- .../ui/components/CircularTimerPanel.kt | 2 +- .../ui/components/SessionIndicatorPanel.kt | 10 ++-- .../ui/settings/PomodoroSettingsPanel.kt | 21 ++++++-- 4 files changed, 63 insertions(+), 19 deletions(-) diff --git a/src/main/kotlin/com/github/akshayashokcode/devfocus/toolWindow/PomodoroToolWindowPanel.kt b/src/main/kotlin/com/github/akshayashokcode/devfocus/toolWindow/PomodoroToolWindowPanel.kt index 5d3a6bb..11964f1 100644 --- a/src/main/kotlin/com/github/akshayashokcode/devfocus/toolWindow/PomodoroToolWindowPanel.kt +++ b/src/main/kotlin/com/github/akshayashokcode/devfocus/toolWindow/PomodoroToolWindowPanel.kt @@ -11,6 +11,7 @@ import com.intellij.ui.components.JBPanel import kotlinx.coroutines.* import kotlinx.coroutines.flow.collectLatest import java.awt.BorderLayout +import java.awt.Dimension import java.awt.FlowLayout import java.awt.Font import java.awt.event.ComponentAdapter @@ -31,6 +32,11 @@ class PomodoroToolWindowPanel(private val project: Project) : JBPanel // Info label showing current mode settings private val infoLabel = JLabel("📊 25 min work • 5 min break").apply { + horizontalAlignment = SwingConstants.CENTER + font = font.deriveFont(Font.BOLD, 12f) + } + + private val sessionTextLabel = JLabel("Session 1 of 4").apply { horizontalAlignment = SwingConstants.CENTER font = font.deriveFont(Font.PLAIN, 12f) } @@ -42,9 +48,18 @@ class PomodoroToolWindowPanel(private val project: Project) : JBPanel private val sessionIndicator = SessionIndicatorPanel() // Control buttons - private val startButton = JButton("Start") - private val pauseButton = JButton("Pause") - private val resetButton = JButton("Reset") + private val startButton = JButton("Start").apply { + preferredSize = Dimension(80, 32) + // Make it a prominent primary button + putClientProperty("JButton.buttonType", "default") + font = font.deriveFont(Font.BOLD) + } + private val pauseButton = JButton("Pause").apply { + preferredSize = Dimension(80, 32) + } + private val resetButton = JButton("Reset").apply { + preferredSize = Dimension(80, 32) + } // Custom settings panel (only visible when Custom mode selected) private val settingsPanel = PomodoroSettingsPanel { session, breakTime, sessions -> @@ -82,24 +97,29 @@ class PomodoroToolWindowPanel(private val project: Project) : JBPanel } // Info panel - val infoPanel = JPanel(FlowLayout(FlowLayout.CENTER)).apply { + val infoPanel = JPanel(FlowLayout(FlowLayout.CENTER, 0, 5)).apply { add(infoLabel) } // Timer panel val timerPanel = JPanel(BorderLayout()).apply { - border = BorderFactory.createEmptyBorder(20, 10, 20, 10) + border = BorderFactory.createEmptyBorder(15, 10, 10, 10) add(circularTimer, BorderLayout.CENTER) } + // Session text label panel + val sessionPanel = JPanel(FlowLayout(FlowLayout.CENTER, 0, 5)).apply { + add(sessionTextLabel) + } + // Progress panel val progressPanel = JPanel(BorderLayout(5, 5)).apply { - border = BorderFactory.createEmptyBorder(0, 20, 10, 20) + border = BorderFactory.createEmptyBorder(5, 20, 10, 20) add(sessionIndicator, BorderLayout.CENTER) } // Button panel - val buttonPanel = JPanel(FlowLayout(FlowLayout.CENTER, 10, 5)).apply { + val buttonPanel = JPanel(FlowLayout(FlowLayout.CENTER, 8, 5)).apply { add(startButton) add(pauseButton) add(resetButton) @@ -110,6 +130,7 @@ class PomodoroToolWindowPanel(private val project: Project) : JBPanel layout = BoxLayout(this, BoxLayout.Y_AXIS) add(infoPanel) add(timerPanel) + add(sessionPanel) add(progressPanel) add(buttonPanel) } @@ -151,7 +172,9 @@ class PomodoroToolWindowPanel(private val project: Project) : JBPanel } private fun updateProgressBar(totalSessions: Int) { - sessionIndicator.updateSessions(timerService.currentSession.value, totalSessions) + val currentSession = timerService.currentSession.value + sessionIndicator.updateSessions(currentSession, totalSessions) + sessionTextLabel.text = "Session $currentSession of $totalSessions" } private fun setupLayoutListener() { @@ -212,6 +235,15 @@ class PomodoroToolWindowPanel(private val project: Project) : JBPanel resetButton.isEnabled = it != PomodoroTimerService.TimerState.IDLE // Disable mode selector when timer is active (running or paused) modeComboBox.isEnabled = it == PomodoroTimerService.TimerState.IDLE + + // Hide custom settings panel when timer is active + if (it != PomodoroTimerService.TimerState.IDLE && modeComboBox.selectedItem == PomodoroMode.CUSTOM) { + settingsPanel.isVisible = false + revalidate() + repaint() + } else { + updateSettingsPanelVisibility() + } } } } @@ -221,6 +253,7 @@ class PomodoroToolWindowPanel(private val project: Project) : JBPanel SwingUtilities.invokeLater { val settings = timerService.getSettings() sessionIndicator.updateSessions(session, settings.sessionsPerRound) + sessionTextLabel.text = "Session $session of ${settings.sessionsPerRound}" } } } 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 2bd455f..888ce85 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 @@ -16,7 +16,7 @@ class CircularTimerPanel : JPanel() { private val backgroundColor = Color(224, 224, 224) // Light gray private val diameter = 180 - private val strokeWidth = 10f + private val strokeWidth = 12f init { preferredSize = Dimension(diameter + 40, diameter + 40) diff --git a/src/main/kotlin/com/github/akshayashokcode/devfocus/ui/components/SessionIndicatorPanel.kt b/src/main/kotlin/com/github/akshayashokcode/devfocus/ui/components/SessionIndicatorPanel.kt index a08dfb3..e833ac1 100644 --- a/src/main/kotlin/com/github/akshayashokcode/devfocus/ui/components/SessionIndicatorPanel.kt +++ b/src/main/kotlin/com/github/akshayashokcode/devfocus/ui/components/SessionIndicatorPanel.kt @@ -9,11 +9,11 @@ class SessionIndicatorPanel : JPanel() { private var totalSessions: Int = 4 private val tomatoSize = 24 - private val spacing = 8 + private val spacing = 10 init { isOpaque = false - preferredSize = Dimension(200, 40) + preferredSize = Dimension(200, 50) } fun updateSessions(current: Int, total: Int) { @@ -61,16 +61,16 @@ class SessionIndicatorPanel : JPanel() { private fun drawCurrentTomato(g2d: Graphics2D, x: Int, y: Int) { // Outlined tomato for current session g2d.color = Color(231, 76, 60) - g2d.stroke = BasicStroke(2.5f) + g2d.stroke = BasicStroke(3f) g2d.drawOval(x + 2, y + 2, tomatoSize - 4, tomatoSize - 4) // Small filled center - g2d.fillOval(x + tomatoSize / 2 - 3, y + tomatoSize / 2 - 3, 6, 6) + g2d.fillOval(x + tomatoSize / 2 - 4, y + tomatoSize / 2 - 4, 8, 8) } private fun drawEmptyCircle(g2d: Graphics2D, x: Int, y: Int) { g2d.color = Color(189, 195, 199) // Light gray - g2d.stroke = BasicStroke(2f) + g2d.stroke = BasicStroke(2.5f) g2d.drawOval(x + 2, y + 2, tomatoSize - 4, tomatoSize - 4) } } \ No newline at end of file diff --git a/src/main/kotlin/com/github/akshayashokcode/devfocus/ui/settings/PomodoroSettingsPanel.kt b/src/main/kotlin/com/github/akshayashokcode/devfocus/ui/settings/PomodoroSettingsPanel.kt index a1b935e..71e5f4b 100644 --- a/src/main/kotlin/com/github/akshayashokcode/devfocus/ui/settings/PomodoroSettingsPanel.kt +++ b/src/main/kotlin/com/github/akshayashokcode/devfocus/ui/settings/PomodoroSettingsPanel.kt @@ -3,20 +3,31 @@ package com.github.akshayashokcode.devfocus.ui.settings import com.github.akshayashokcode.devfocus.util.SettingsValidationResult import com.github.akshayashokcode.devfocus.util.validateSettings import java.awt.Color +import java.awt.Dimension import java.awt.GridLayout import javax.swing.* import javax.swing.border.LineBorder class PomodoroSettingsPanel( private val applySettingsCallback: (Int, Int, Int) -> Unit -) : JPanel(GridLayout(4, 2, 5, 5)) { +) : JPanel(GridLayout(4, 2, 8, 5)) { - private val sessionField = JTextField("25") - private val breakField = JTextField("5") - private val sessionsField = JTextField("4") - private val applyButton = JButton("Apply") + private val sessionField = JTextField("25").apply { + preferredSize = Dimension(60, 28) + } + private val breakField = JTextField("5").apply { + preferredSize = Dimension(60, 28) + } + private val sessionsField = JTextField("4").apply { + preferredSize = Dimension(60, 28) + } + private val applyButton = JButton("Apply").apply { + preferredSize = Dimension(100, 32) + } init { + border = BorderFactory.createEmptyBorder(10, 15, 10, 15) + add(JLabel("Session Duration (min):")) add(sessionField) add(JLabel("Break Duration (min):")) From af2606a964988d3f940c57edbbf36d6d07b36aa0 Mon Sep 17 00:00:00 2001 From: akshay ashok Date: Sat, 21 Feb 2026 17:47:15 +0530 Subject: [PATCH 3/5] #15 Polished UI --- .../services/pomodoro/PomodoroTimerService.kt | 28 ++++--- .../toolWindow/PomodoroToolWindowPanel.kt | 21 +++++- .../ui/components/CircularTimerPanel.kt | 2 +- .../ui/components/SessionIndicatorPanel.kt | 75 +++++++++++-------- 4 files changed, 83 insertions(+), 43 deletions(-) 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 4ff1091..640417e 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 @@ -31,7 +31,7 @@ class PomodoroTimerService(private val project: Project) { private var job: Job? = null private var settings = PomodoroMode.CLASSIC.toSettings() - private var currentPhase = TimerPhase.WORK + private var internalPhase = TimerPhase.WORK private var remainingTimeMs: Long = TimeUnit.MINUTES.toMillis(settings.sessionMinutes.toLong()) private val _timeLeft = MutableStateFlow(formatTime(remainingTimeMs)) @@ -43,6 +43,9 @@ class PomodoroTimerService(private val project: Project) { private val _currentSession = MutableStateFlow(1) val currentSession: StateFlow = _currentSession + private val _currentPhase = MutableStateFlow(TimerPhase.WORK) + val currentPhase: StateFlow = _currentPhase + private val _settings = MutableStateFlow(settings) val settingsFlow: StateFlow = _settings @@ -70,7 +73,7 @@ class PomodoroTimerService(private val project: Project) { val currentSessionNum = _currentSession.value val totalSessions = settings.sessionsPerRound - if (currentPhase == TimerPhase.WORK) { + if (internalPhase == TimerPhase.WORK) { // Work session complete if (currentSessionNum >= totalSessions) { // Last session complete - all done! @@ -84,23 +87,27 @@ class PomodoroTimerService(private val project: Project) { .notify(project) // Reset to initial state - currentPhase = TimerPhase.WORK + internalPhase = TimerPhase.WORK + _currentPhase.value = TimerPhase.WORK _currentSession.value = 1 remainingTimeMs = TimeUnit.MINUTES.toMillis(settings.sessionMinutes.toLong()) _timeLeft.value = formatTime(remainingTimeMs) } else { // Work session complete - start break + _currentSession.value = currentSessionNum + 1 + NotificationGroupManager.getInstance() .getNotificationGroup(NOTIFICATION_GROUP_ID) .createNotification( - "\uD83C\uDF45 Session Complete!", + "\uD83C\uDF45 Session $currentSessionNum Complete!", "Great work! Starting ${settings.breakMinutes}-minute break ☕.", NotificationType.INFORMATION ) .notify(project) // Start break timer - currentPhase = TimerPhase.BREAK + internalPhase = TimerPhase.BREAK + _currentPhase.value = TimerPhase.BREAK remainingTimeMs = TimeUnit.MINUTES.toMillis(settings.breakMinutes.toLong()) _timeLeft.value = formatTime(remainingTimeMs) start() @@ -119,7 +126,8 @@ class PomodoroTimerService(private val project: Project) { .notify(project) // Start next work session - currentPhase = TimerPhase.WORK + internalPhase = TimerPhase.WORK + _currentPhase.value = TimerPhase.WORK _currentSession.value = currentSessionNum + 1 remainingTimeMs = TimeUnit.MINUTES.toMillis(settings.sessionMinutes.toLong()) _timeLeft.value = formatTime(remainingTimeMs) @@ -141,7 +149,8 @@ class PomodoroTimerService(private val project: Project) { job = null // Reset to initial state - currentPhase = TimerPhase.WORK + internalPhase = TimerPhase.WORK + _currentPhase.value = TimerPhase.WORK remainingTimeMs = TimeUnit.MINUTES.toMillis(settings.sessionMinutes.toLong()) _timeLeft.value = formatTime(remainingTimeMs) _currentSession.value = 1 @@ -162,7 +171,8 @@ class PomodoroTimerService(private val project: Project) { settings = newSettings _settings.value = newSettings - currentPhase = TimerPhase.WORK + internalPhase = TimerPhase.WORK + _currentPhase.value = TimerPhase.WORK remainingTimeMs = TimeUnit.MINUTES.toMillis(newSettings.sessionMinutes.toLong()) _timeLeft.value = formatTime(remainingTimeMs) _currentSession.value = 1 @@ -176,7 +186,7 @@ class PomodoroTimerService(private val project: Project) { fun getSettings(): PomodoroSettings = settings fun getProgress(): Float { - val totalMs = if (currentPhase == TimerPhase.WORK) { + val totalMs = if (internalPhase == TimerPhase.WORK) { TimeUnit.MINUTES.toMillis(settings.sessionMinutes.toLong()) } else { TimeUnit.MINUTES.toMillis(settings.breakMinutes.toLong()) diff --git a/src/main/kotlin/com/github/akshayashokcode/devfocus/toolWindow/PomodoroToolWindowPanel.kt b/src/main/kotlin/com/github/akshayashokcode/devfocus/toolWindow/PomodoroToolWindowPanel.kt index 11964f1..147740f 100644 --- a/src/main/kotlin/com/github/akshayashokcode/devfocus/toolWindow/PomodoroToolWindowPanel.kt +++ b/src/main/kotlin/com/github/akshayashokcode/devfocus/toolWindow/PomodoroToolWindowPanel.kt @@ -38,7 +38,7 @@ class PomodoroToolWindowPanel(private val project: Project) : JBPanel private val sessionTextLabel = JLabel("Session 1 of 4").apply { horizontalAlignment = SwingConstants.CENTER - font = font.deriveFont(Font.PLAIN, 12f) + font = font.deriveFont(Font.BOLD, 14f) } // Circular timer display @@ -72,6 +72,7 @@ class PomodoroToolWindowPanel(private val project: Project) : JBPanel private var stateJob: Job? = null private var timeJob: Job? = null private var sessionJob: Job? = null + private var phaseJob: Job? = null init { buildUI() @@ -222,7 +223,8 @@ class PomodoroToolWindowPanel(private val project: Project) : JBPanel timerService.timeLeft.collectLatest { time -> SwingUtilities.invokeLater { val progress = timerService.getProgress() - circularTimer.updateTimer(time, progress, false) + val isBreak = timerService.currentPhase.value == PomodoroTimerService.TimerPhase.BREAK + circularTimer.updateTimer(time, progress, isBreak) } } } @@ -252,17 +254,30 @@ class PomodoroToolWindowPanel(private val project: Project) : JBPanel timerService.currentSession.collectLatest { session -> SwingUtilities.invokeLater { val settings = timerService.getSettings() - sessionIndicator.updateSessions(session, settings.sessionsPerRound) + val isBreak = timerService.currentPhase.value == PomodoroTimerService.TimerPhase.BREAK + sessionIndicator.updateSessions(session, settings.sessionsPerRound, isBreak) sessionTextLabel.text = "Session $session of ${settings.sessionsPerRound}" } } } + + phaseJob = scope.launch { + timerService.currentPhase.collectLatest { phase -> + SwingUtilities.invokeLater { + val settings = timerService.getSettings() + val session = timerService.currentSession.value + val isBreak = phase == PomodoroTimerService.TimerPhase.BREAK + sessionIndicator.updateSessions(session, settings.sessionsPerRound, isBreak) + } + } + } } fun dispose() { stateJob?.cancel() timeJob?.cancel() sessionJob?.cancel() + phaseJob?.cancel() scope.cancel() } } \ 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 888ce85..0960d30 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 @@ -12,7 +12,7 @@ class CircularTimerPanel : JPanel() { // Colors following UX best practices private val workColor = Color(74, 144, 226) // Blue for focus/work - private val breakColor = Color(80, 200, 120) // Green for rest + private val breakColor = Color(243, 156, 18) // Orange for rest private val backgroundColor = Color(224, 224, 224) // Light gray private val diameter = 180 diff --git a/src/main/kotlin/com/github/akshayashokcode/devfocus/ui/components/SessionIndicatorPanel.kt b/src/main/kotlin/com/github/akshayashokcode/devfocus/ui/components/SessionIndicatorPanel.kt index e833ac1..b7c6e61 100644 --- a/src/main/kotlin/com/github/akshayashokcode/devfocus/ui/components/SessionIndicatorPanel.kt +++ b/src/main/kotlin/com/github/akshayashokcode/devfocus/ui/components/SessionIndicatorPanel.kt @@ -7,18 +7,25 @@ class SessionIndicatorPanel : JPanel() { private var currentSession: Int = 1 private var totalSessions: Int = 4 + private var isBreakTime: Boolean = false - private val tomatoSize = 24 - private val spacing = 10 + private val completedColor = Color(39, 174, 96) + private val workColor = Color(74, 144, 226) + private val breakColor = Color(243, 156, 18) + private val upcomingColor = Color(149, 165, 166) + + private val indicatorSize = 30 + private val spacing = 12 init { isOpaque = false preferredSize = Dimension(200, 50) } - fun updateSessions(current: Int, total: Int) { + fun updateSessions(current: Int, total: Int, isBreak: Boolean = false) { this.currentSession = current this.totalSessions = total + this.isBreakTime = isBreak repaint() } @@ -28,49 +35,57 @@ class SessionIndicatorPanel : JPanel() { g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON) - val totalWidth = (tomatoSize * totalSessions) + (spacing * (totalSessions - 1)) + val totalWidth = (indicatorSize * totalSessions) + (spacing * (totalSessions - 1)) val startX = (width - totalWidth) / 2 - val startY = (height - tomatoSize) / 2 + val startY = (height - indicatorSize) / 2 for (i in 1..totalSessions) { - val x = startX + (i - 1) * (tomatoSize + spacing) + val x = startX + (i - 1) * (indicatorSize + spacing) if (i < currentSession) { - // Completed session - filled tomato - drawFilledTomato(g2d, x, startY) + // Completed session + drawCompletedSession(g2d, x, startY) } else if (i == currentSession) { - // Current session - outlined tomato with pulse effect - drawCurrentTomato(g2d, x, startY) + // Current session + drawCurrentSession(g2d, x, startY) } else { - // Future session - empty circle - drawEmptyCircle(g2d, x, startY) + // Upcoming session + drawUpcomingSession(g2d, x, startY) } } } - private fun drawFilledTomato(g2d: Graphics2D, x: Int, y: Int) { - g2d.color = Color(231, 76, 60) // Tomato red - g2d.fillOval(x, y, tomatoSize, tomatoSize) + private fun drawCompletedSession(g2d: Graphics2D, x: Int, y: Int) { + g2d.color = completedColor + g2d.fillOval(x, y, indicatorSize, indicatorSize) - // Add small leaf/stem on top - g2d.color = Color(46, 204, 113) // Green - val leafSize = 6 - g2d.fillOval(x + tomatoSize / 2 - leafSize / 2, y - 2, leafSize, leafSize) - } + // Draw white checkmark + g2d.color = Color.WHITE + g2d.stroke = BasicStroke(2.5f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND) + + val checkSize = indicatorSize / 2 + val centerX = x + indicatorSize / 2 + val centerY = y + indicatorSize / 2 - private fun drawCurrentTomato(g2d: Graphics2D, x: Int, y: Int) { - // Outlined tomato for current session - g2d.color = Color(231, 76, 60) - g2d.stroke = BasicStroke(3f) - g2d.drawOval(x + 2, y + 2, tomatoSize - 4, tomatoSize - 4) + val x1 = centerX - checkSize / 3 + val y1 = centerY + val x2 = centerX - checkSize / 6 + val y2 = centerY + checkSize / 3 + g2d.drawLine(x1, y1, x2, y2) + + val x3 = centerX + checkSize / 2 + val y3 = centerY - checkSize / 3 + g2d.drawLine(x2, y2, x3, y3) + } - // Small filled center - g2d.fillOval(x + tomatoSize / 2 - 4, y + tomatoSize / 2 - 4, 8, 8) + private fun drawCurrentSession(g2d: Graphics2D, x: Int, y: Int) { + g2d.color = if (isBreakTime) breakColor else workColor + g2d.fillOval(x, y, indicatorSize, indicatorSize) } - private fun drawEmptyCircle(g2d: Graphics2D, x: Int, y: Int) { - g2d.color = Color(189, 195, 199) // Light gray + private fun drawUpcomingSession(g2d: Graphics2D, x: Int, y: Int) { + g2d.color = upcomingColor g2d.stroke = BasicStroke(2.5f) - g2d.drawOval(x + 2, y + 2, tomatoSize - 4, tomatoSize - 4) + g2d.drawOval(x + 2, y + 2, indicatorSize - 4, indicatorSize - 4) } } \ No newline at end of file From 78011ccac6732d240bad171ef323a2aa031ed789 Mon Sep 17 00:00:00 2001 From: akshay ashok Date: Sat, 21 Feb 2026 18:04:17 +0530 Subject: [PATCH 4/5] #15 Polished UI --- .../services/pomodoro/PomodoroTimerService.kt | 4 ++-- .../toolWindow/DevFocusToolWindowFactory.kt | 1 + .../toolWindow/PomodoroToolWindowPanel.kt | 20 +++++++++++++------ 3 files changed, 17 insertions(+), 8 deletions(-) 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 640417e..47fb800 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 @@ -99,7 +99,7 @@ class PomodoroTimerService(private val project: Project) { NotificationGroupManager.getInstance() .getNotificationGroup(NOTIFICATION_GROUP_ID) .createNotification( - "\uD83C\uDF45 Session $currentSessionNum Complete!", + "✅ Session $currentSessionNum Complete!", "Great work! Starting ${settings.breakMinutes}-minute break ☕.", NotificationType.INFORMATION ) @@ -120,7 +120,7 @@ class PomodoroTimerService(private val project: Project) { .getNotificationGroup(NOTIFICATION_GROUP_ID) .createNotification( "☕ Break Complete!", - "Starting session ${currentSessionNum + 1} of $totalSessions.", + "Starting session $currentSessionNum of $totalSessions.", NotificationType.INFORMATION ) .notify(project) diff --git a/src/main/kotlin/com/github/akshayashokcode/devfocus/toolWindow/DevFocusToolWindowFactory.kt b/src/main/kotlin/com/github/akshayashokcode/devfocus/toolWindow/DevFocusToolWindowFactory.kt index ddaf389..7826f80 100644 --- a/src/main/kotlin/com/github/akshayashokcode/devfocus/toolWindow/DevFocusToolWindowFactory.kt +++ b/src/main/kotlin/com/github/akshayashokcode/devfocus/toolWindow/DevFocusToolWindowFactory.kt @@ -11,6 +11,7 @@ class DevFocusToolWindowFactory : ToolWindowFactory { override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) { val panel = PomodoroToolWindowPanel(project) val content = ContentFactory.getInstance().createContent(panel, "", false) + content.setDisposer(panel) toolWindow.contentManager.addContent(content) } diff --git a/src/main/kotlin/com/github/akshayashokcode/devfocus/toolWindow/PomodoroToolWindowPanel.kt b/src/main/kotlin/com/github/akshayashokcode/devfocus/toolWindow/PomodoroToolWindowPanel.kt index 147740f..cf4f23a 100644 --- a/src/main/kotlin/com/github/akshayashokcode/devfocus/toolWindow/PomodoroToolWindowPanel.kt +++ b/src/main/kotlin/com/github/akshayashokcode/devfocus/toolWindow/PomodoroToolWindowPanel.kt @@ -6,6 +6,7 @@ import com.github.akshayashokcode.devfocus.services.pomodoro.PomodoroTimerServic import com.github.akshayashokcode.devfocus.ui.components.CircularTimerPanel import com.github.akshayashokcode.devfocus.ui.components.SessionIndicatorPanel import com.github.akshayashokcode.devfocus.ui.settings.PomodoroSettingsPanel +import com.intellij.openapi.Disposable import com.intellij.openapi.project.Project import com.intellij.ui.components.JBPanel import kotlinx.coroutines.* @@ -18,7 +19,7 @@ import java.awt.event.ComponentAdapter import java.awt.event.ComponentEvent import javax.swing.* -class PomodoroToolWindowPanel(private val project: Project) : JBPanel>(BorderLayout()) { +class PomodoroToolWindowPanel(private val project: Project) : JBPanel>(BorderLayout()), Disposable { private val timerService = project.getService(PomodoroTimerService::class.java) ?: error("PomodoroTimerService not available") @@ -235,15 +236,22 @@ class PomodoroToolWindowPanel(private val project: Project) : JBPanel startButton.isEnabled = it != PomodoroTimerService.TimerState.RUNNING pauseButton.isEnabled = it == PomodoroTimerService.TimerState.RUNNING resetButton.isEnabled = it != PomodoroTimerService.TimerState.IDLE - // Disable mode selector when timer is active (running or paused) - modeComboBox.isEnabled = it == PomodoroTimerService.TimerState.IDLE + + // Check if we're truly idle (session and work phase) or just transitioning + val currentSession = timerService.currentSession.value + val currentPhase = timerService.currentPhase.value + val isTrulyIdle = it == PomodoroTimerService.TimerState.IDLE && + currentSession == 1 && + currentPhase == PomodoroTimerService.TimerPhase.WORK + + modeComboBox.isEnabled = isTrulyIdle // Hide custom settings panel when timer is active - if (it != PomodoroTimerService.TimerState.IDLE && modeComboBox.selectedItem == PomodoroMode.CUSTOM) { + if (!isTrulyIdle && modeComboBox.selectedItem == PomodoroMode.CUSTOM) { settingsPanel.isVisible = false revalidate() repaint() - } else { + } else if (isTrulyIdle){ updateSettingsPanelVisibility() } } @@ -273,7 +281,7 @@ class PomodoroToolWindowPanel(private val project: Project) : JBPanel } } - fun dispose() { + override fun dispose() { stateJob?.cancel() timeJob?.cancel() sessionJob?.cancel() From 57c78b4ab369570ec9352c8738907cf50cf17cf0 Mon Sep 17 00:00:00 2001 From: akshay ashok Date: Sat, 21 Feb 2026 18:12:31 +0530 Subject: [PATCH 5/5] #15 Polished UI --- .../com/github/akshayashokcode/devfocus/model/PomodoroMode.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/github/akshayashokcode/devfocus/model/PomodoroMode.kt b/src/main/kotlin/com/github/akshayashokcode/devfocus/model/PomodoroMode.kt index 374ea81..e61b709 100644 --- a/src/main/kotlin/com/github/akshayashokcode/devfocus/model/PomodoroMode.kt +++ b/src/main/kotlin/com/github/akshayashokcode/devfocus/model/PomodoroMode.kt @@ -11,7 +11,7 @@ enum class PomodoroMode( ) { CLASSIC( displayName = "Classic Pomodoro", - emoji = "🍅", + emoji = "\uD83C\uDFAF", sessionMinutes = 25, breakMinutes = 5, sessionsPerRound = 4,