Files
noteflow/spikes/spike_01_ui_tray_hotkeys/demo.py
Travis Vasceannie af1285b181 Add initial project structure and files
- Introduced .python-version for Python version management.
- Added AGENTS.md for documentation on agent usage and best practices.
- Created alembic.ini for database migration configurations.
- Implemented main.py as the entry point for the application.
- Established pyproject.toml for project dependencies and configurations.
- Initialized README.md for project overview.
- Generated uv.lock for dependency locking.
- Documented milestones and specifications in docs/milestones.md and docs/spec.md.
- Created logs/status_line.json for logging status information.
- Added initial spike implementations for UI tray hotkeys, audio capture, ASR latency, and encryption validation.
- Set up NoteFlow core structure in src/noteflow with necessary modules and services.
- Developed test suite in tests directory for application, domain, infrastructure, and integration testing.
- Included initial migration scripts in infrastructure/persistence/migrations for database setup.
- Established security protocols in infrastructure/security for key management and encryption.
- Implemented audio infrastructure for capturing and processing audio data.
- Created converters for ASR and ORM in infrastructure/converters.
- Added export functionality for different formats in infrastructure/export.
- Ensured all new files are included in the repository for future development.
2025-12-17 18:28:59 +00:00

254 lines
7.5 KiB
Python

"""Interactive UI + Tray + Hotkeys demo for Spike 1.
Run with: python -m spikes.spike_01_ui_tray_hotkeys.demo
Features:
- Flet window with Start/Stop buttons
- System tray icon with context menu
- Global hotkey support (Ctrl+Shift+R)
- Notifications on state changes
"""
from __future__ import annotations
import logging
import queue
import sys
import threading
from enum import Enum, auto
import flet as ft
from .hotkey_impl import PynputHotkeyManager
from .protocols import TrayIcon, TrayMenuItem
from .tray_impl import PystrayController
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
logger = logging.getLogger(__name__)
class AppState(Enum):
"""Application state."""
IDLE = auto()
RECORDING = auto()
class NoteFlowDemo:
"""Demo application combining Flet UI, system tray, and hotkeys."""
def __init__(self) -> None:
"""Initialize the demo application."""
self.state = AppState.IDLE
self.tray = PystrayController(app_name="NoteFlow Demo")
self.hotkey_manager = PynputHotkeyManager()
# Queue for cross-thread communication
self._event_queue: queue.Queue[str] = queue.Queue()
# Flet page reference (set when app starts)
self._page: ft.Page | None = None
self._status_text: ft.Text | None = None
self._toggle_button: ft.ElevatedButton | None = None
def _update_ui(self) -> None:
"""Update UI elements based on current state."""
if self._page is None:
return
if self.state == AppState.RECORDING:
if self._status_text:
self._status_text.value = "Recording..."
self._status_text.color = ft.Colors.RED
if self._toggle_button:
self._toggle_button.text = "Stop Recording"
self._toggle_button.bgcolor = ft.Colors.RED
self.tray.set_icon(TrayIcon.RECORDING)
self.tray.set_tooltip("NoteFlow - Recording")
else:
if self._status_text:
self._status_text.value = "Idle"
self._status_text.color = ft.Colors.GREY
if self._toggle_button:
self._toggle_button.text = "Start Recording"
self._toggle_button.bgcolor = ft.Colors.BLUE
self.tray.set_icon(TrayIcon.IDLE)
self.tray.set_tooltip("NoteFlow - Idle")
self._page.update()
def _toggle_recording(self) -> None:
"""Toggle recording state."""
if self.state == AppState.IDLE:
self.state = AppState.RECORDING
logger.info("Started recording")
self.tray.notify("NoteFlow", "Recording started")
else:
self.state = AppState.IDLE
logger.info("Stopped recording")
self.tray.notify("NoteFlow", "Recording stopped")
self._update_ui()
def _on_toggle_click(self, e: ft.ControlEvent) -> None:
"""Handle toggle button click."""
self._toggle_recording()
def _on_hotkey(self) -> None:
"""Handle global hotkey press."""
logger.info("Hotkey pressed!")
# Queue event for main thread
self._event_queue.put("toggle")
def _process_events(self) -> None:
"""Process queued events (called periodically from UI thread)."""
try:
while True:
event = self._event_queue.get_nowait()
if event == "toggle":
self._toggle_recording()
elif event == "quit":
self._cleanup()
if self._page:
self._page.window.close()
except queue.Empty:
pass
def _setup_tray_menu(self) -> None:
"""Set up the system tray context menu."""
menu_items = [
TrayMenuItem(
label="Start Recording" if self.state == AppState.IDLE else "Stop Recording",
callback=self._toggle_recording,
),
TrayMenuItem(label="", callback=lambda: None, separator=True),
TrayMenuItem(
label="Show Window",
callback=lambda: self._event_queue.put("show"),
),
TrayMenuItem(label="", callback=lambda: None, separator=True),
TrayMenuItem(
label="Quit",
callback=lambda: self._event_queue.put("quit"),
),
]
self.tray.set_menu(menu_items)
def _cleanup(self) -> None:
"""Clean up resources."""
self.hotkey_manager.unregister_all()
self.tray.stop()
def _build_ui(self, page: ft.Page) -> None:
"""Build the Flet UI."""
self._page = page
page.title = "NoteFlow Demo - Spike 1"
page.window.width = 400
page.window.height = 300
page.theme_mode = ft.ThemeMode.DARK
# Status text
self._status_text = ft.Text(
value="Idle",
size=24,
weight=ft.FontWeight.BOLD,
color=ft.Colors.GREY,
)
# Toggle button
self._toggle_button = ft.ElevatedButton(
text="Start Recording",
icon=ft.Icons.MIC,
on_click=self._on_toggle_click,
bgcolor=ft.Colors.BLUE,
color=ft.Colors.WHITE,
width=200,
height=50,
)
# Hotkey info
hotkey_text = ft.Text(
value="Hotkey: Ctrl+Shift+R",
size=14,
color=ft.Colors.GREY_400,
)
# Layout
page.add(
ft.Column(
controls=[
ft.Container(height=30),
self._status_text,
ft.Container(height=20),
self._toggle_button,
ft.Container(height=30),
hotkey_text,
ft.Text(
value="System tray icon is active",
size=12,
color=ft.Colors.GREY_600,
),
],
horizontal_alignment=ft.CrossAxisAlignment.CENTER,
alignment=ft.MainAxisAlignment.CENTER,
)
)
# Set up event polling
def poll_events() -> None:
self._process_events()
# Poll events every 100ms
page.run_task(self._poll_loop)
async def _poll_loop(self) -> None:
"""Async loop to poll events."""
import asyncio
while True:
self._process_events()
await asyncio.sleep(0.1)
def run(self) -> None:
"""Run the demo application."""
logger.info("Starting NoteFlow Demo")
# Start system tray
self.tray.start()
self._setup_tray_menu()
# Register global hotkey
try:
self.hotkey_manager.register("ctrl+shift+r", self._on_hotkey)
logger.info("Registered hotkey: Ctrl+Shift+R")
except Exception as e:
logger.warning("Failed to register hotkey: %s", e)
try:
# Run Flet app
ft.app(target=self._build_ui)
finally:
self._cleanup()
logger.info("Demo ended")
def main() -> None:
"""Run the UI + Tray + Hotkeys demo."""
print("=== NoteFlow Demo - Spike 1 ===")
print("Features:")
print(" - Flet window with Start/Stop buttons")
print(" - System tray icon with context menu")
print(" - Global hotkey: Ctrl+Shift+R")
print()
demo = NoteFlowDemo()
demo.run()
if __name__ == "__main__":
main()