Building a Pomodoro Timer in Python: My Learning Journey

Andrew Luo Weimin

technologycodingproductivity

828 Words 3 Minutes, 45 Seconds

2025-02-09 08:16 +0000


As I continue my journey in learning Python, I took on the challenge of creating a Pomodoro timer. This project not only helped me apply my Python skills but also introduced me to GUI development using Tkinter. Let me share my experience and some insights I gained along the way.

The Pomodoro Technique and Why I Chose This Project

The Pomodoro Technique is a time management method that uses a timer to break work into intervals, traditionally 25 minutes in length, separated by short breaks. There wasn’t any suitable app that I like. I thought building a Pomodoro timer would be both practical and educational.

Key Features of the Pomodoro Timer

My Pomodoro timer includes the following features:

  • A customizable timer for work sessions and breaks
  • A simple, clean GUI
  • The ability to start, pause, and reset the timer
  • Using my favorite Nord color palette

Learning Experience and Challenges

GUI Development with Tkinter

One of the most significant learning curves was working with Tkinter for the first time. I had to familiarize myself with concepts like:

  • Creating and configuring windows and frames
  • Adding and styling widgets (labels, buttons)
  • Implementing event handling for user interactions

Time Management in Python

Implementing the timer functionality taught me about:

  • Using the time module for delays and time tracking
  • Converting between different time units (seconds, minutes)
  • Updating the GUI in real-time

Reflections on Learning Python

Building this Pomodoro timer has been an enlightening experience in my Python learning journey. Here are some key takeaways:

  1. Practice is crucial: Reading about Python concepts is important, but actually applying them in a project solidifies understanding.

  2. Start small, then expand: I began with a basic timer and gradually added features, which helped manage complexity.

  3. Embrace debugging: Troubleshooting issues, especially in GUI development, significantly improved my problem-solving skills.

  4. Customize and experiment: Adding personal touches, like the Nord color scheme, made the project more engaging and fun.

  5. Learn from others: Researching similar projects and studying their code provided valuable insights and best practices.

Happy coding, and may your Pomodoros be productive!


import tkinter as tk
from sys import platform

class PomodoroTimer:
    def __init__(self):
        self.window = tk.Tk()
        self.window.overrideredirect(True)
        self.window.attributes('-topmost', True)
        
        # Set window size and position
        # self.window.geometry("200x250")

        # Nord color palette
        self.polar_night = {
            'base': "#2E3440",  # Dark base background
            'lighter': "#3B4252",  # Lighter background for buttons
            'hover': "#434C5E"   # Button hover color
        }
        
        self.snow_storm = {
            'text': "#D8DEE9",  # Main text color
            'bright': "#ECEFF4"  # Bright text for emphasis
        }
        
        self.frost = "#88C0D0"  # Accent color for special elements
        
        # Set window background
        self.window.configure(bg=self.polar_night['base'])

        # Create custom title bar
        self.title_bar = tk.Frame(
            self.window,
            bg=self.polar_night['base'],
            relief='flat',
            height=18
        )
        self.title_bar.pack(fill=tk.X)

        # Add title text
        self.title_label = tk.Label(
            self.title_bar,
            text="Pomodoro Timer",
            bg=self.polar_night['base'],
            fg=self.snow_storm['text'],
            font=("Arial", 12)
        )
        self.title_label.pack(side=tk.LEFT, pady=2, padx=10)

        # Add window controls
        self.close_button = tk.Button(
            self.title_bar,
            text="×",
            command=self.window.destroy,
            bg=self.polar_night['base'],
            fg=self.snow_storm['text'],
            relief='flat',
            font=("Arial", 12),
            width=3,
            activebackground=self.polar_night['hover'],
            activeforeground=self.snow_storm['bright']
        )
        self.close_button.pack(side=tk.RIGHT, pady=0)

        # Make window draggable
        self.title_bar.bind('<Button-1>', self.start_move)
        self.title_bar.bind('<B1-Motion>', self.on_move)
        self.title_label.bind('<Button-1>', self.start_move)
        self.title_label.bind('<B1-Motion>', self.on_move)
        
        # Container for main content
        self.main_container = tk.Frame(
            self.window,
            bg=self.polar_night['base']
        )
        self.main_container.pack(fill=tk.BOTH, expand=True)

        # Initialize timer variables
        self.time_left = 25 * 60
        self.timer_running = False
        self.sessions_completed = 0
        self.current_session = "Work"
        self.timer_id = None
        
        # Create UI elements with Nord styling
        self.timer_label = tk.Label(
            self.window, 
            text="25:00", 
            font=("Arial", 40),
            bg=self.polar_night['base'],
            fg=self.snow_storm['bright']
        )
        self.timer_label.pack(pady=10)
        
        self.session_label = tk.Label(
            self.window, 
            text="Work", 
            font=("Arial", 20),
            bg=self.polar_night['base'],
            fg=self.frost
        )
        self.session_label.pack()
        
        # Control buttons with Nord styling
        button_style = {
            'bg': self.polar_night['lighter'],
            'fg': self.snow_storm['text'],
            'padx': 20,
            'pady': 10,
            'relief': 'flat',
            'activebackground': self.polar_night['hover'],
            'activeforeground': self.snow_storm['bright']
        }
        
        # Create a frame for buttons
        button_frame = tk.Frame(
            self.main_container,
            bg=self.polar_night['base']
        )
        button_frame.pack(pady=10)

        self.start_button = tk.Button(
            self.window, 
            text="Start", 
            command=self.start_timer,
            **button_style
        )
        self.start_button.pack(side=tk.LEFT, padx=10)
        
        self.pause_button = tk.Button(
            self.window, 
            text="Pause", 
            command=self.pause_timer,
            **button_style
        )
        self.pause_button.pack(side=tk.LEFT, padx=10)
        
        self.reset_button = tk.Button(
            self.window, 
            text="Reset", 
            command=self.reset_timer,
            **button_style
        )
        self.reset_button.pack(side=tk.LEFT, padx=10)
        
        self.window.mainloop()

    def start_move(self, event):
        """Called when the user clicks on the title bar"""
        self.x = event.x
        self.y = event.y

    def on_move(self, event):
        """Called when the user drags the title bar"""
        deltax = event.x - self.x
        deltay = event.y - self.y
        x = self.window.winfo_x() + deltax
        y = self.window.winfo_y() + deltay
        self.window.geometry(f"+{x}+{y}")

    def start_timer(self):
        """Start the timer if it's not already running"""
        if not self.timer_running:
            self.timer_running = True
            self.timer_id = self.window.after(1000, self.countdown)

    def pause_timer(self):
        """Pause the running timer"""
        if self.timer_running:
            self.timer_running = False
            if self.timer_id:
                self.window.after_cancel(self.timer_id)
                self.timer_id = None

    def reset_timer(self):
        """Reset timer to initial state"""
        self.pause_timer()
        self.sessions_completed = 0
        self.current_session = "Work"
        self.time_left = 25 * 60
        self.update_display()

    def switch_session(self):
        """Switch between work and break sessions"""
        if self.current_session == "Work":
            self.sessions_completed += 1
            if self.sessions_completed % 4 == 0:
                self.current_session = "Long Break"
                self.time_left = 15 * 60
            else:
                self.current_session = "Short Break"
                self.time_left = 5 * 60
        else:
            self.current_session = "Work"
            self.time_left = 25 * 60
        
        self.update_display()
        self.start_timer()

    def countdown(self):
        """Handle the countdown mechanism"""
        if self.timer_running:
            self.time_left -= 1
            self.update_display()
            
            if self.time_left > 0:
                self.timer_id = self.window.after(1000, self.countdown)
            else:
                self.timer_running = False
                self.switch_session()

    def update_display(self):
        """Update the timer and session labels"""
        minutes = self.time_left // 60
        seconds = self.time_left % 60
        self.timer_label.config(text=f"{minutes:02d}:{seconds:02d}")
        self.session_label.config(text=self.current_session)

if __name__ == "__main__":
    PomodoroTimer()