A Pomodoro productivity timer that lives entirely in the macOS menu bar. Built with Flutter + Riverpod.
- Live countdown in the menu bar title (25:00 → 00:00)
- Left-click tray icon → toggle popup panel
- Right-click → context menu (Open / Quit)
- Wall-clock timer (never drifts when backgrounded)
- Three modes: 25m Focus, 5m Short Break, 15m Long Break
- Animated SVG ring with urgent-red under 60 s
- Task list with drag-to-reorder, active task highlight, and history
- Collapsible completed-task history with timestamps
- Motivational quotes (Forismatic API, cached up to 20, navigable)
- Bell-chord alarm synthesised in-memory (C5 + E5 + G5 sine oscillators)
- Dark/light mode toggle, persisted across launches
- No Dock icon (
LSUIElement = true) - All state persisted via
shared_preferences
| Tool | Version |
|---|---|
| Flutter | ≥ 3.22 |
| Dart | ≥ 3.3 |
| Xcode | ≥ 15 |
| macOS | ≥ 10.14 |
Install Flutter: https://docs.flutter.dev/get-started/install/macos
cd pomo-flutter
flutter pub get
flutter run -d macos
# Release build:
flutter build macos --release
# Output: build/macos/Build/Products/Release/Pomo.appbrew install create-dmg
create-dmg \
--volname "Pomo" \
--window-size 500 300 \
--icon-size 128 \
--icon "Pomo.app" 130 130 \
--app-drop-link 370 130 \
"Pomo.dmg" \
"build/macos/Build/Products/Release/"lib/
main.dart # App entry point, tray + window bootstrap
models/ # Task, HistoryItem, Quote + JSON serialisation
providers/ # Riverpod notifiers (timer, tasks, history, quotes, theme)
services/ # Tray wrapper, audio synthesis, icon generator
utils/ # Colour constants, ThemeData factory
widgets/ # Panel, header, alarm banner, timer ring, tasks, history, quotes
macos/
Runner/Info.plist # LSUIElement=true hides Dock icon
Runner/AppDelegate.swift # Keeps app alive after window close
Runner/DebugProfile.entitlements
Runner/Release.entitlements
- Wall-clock timer:
deadline = DateTime.now() + duration, polled every 500 ms — catches up automatically after backgrounding. - No audio files: bell chord synthesised in-memory as a WAV buffer (C5 + E5 + G5 sine oscillators with exponential decay) fed to flutter_soloud.
- Tray icon: generated programmatically at startup via
dart:uicanvas — noassets/directory needed. - State: Riverpod
NotifierProviders; shared_preferences write after every mutation.
tray_manager and window_manager are both cross-platform. Remove LSUIElement from Info.plist for non-macOS targets; skipTaskbar: true (already set in WindowOptions) does the equivalent job. The Swift AppDelegate is macOS-only; other platforms use Flutter's default embedding.