Started working on the local ncurses UI. Also logging...
This commit is contained in:
parent
35d1e76ba0
commit
0eb5204833
@ -11,23 +11,15 @@ import re
|
|||||||
|
|
||||||
from alarm_storage import AlarmStorage
|
from alarm_storage import AlarmStorage
|
||||||
from data_classes import RepeatRule, Snooze, Metadata, Alarm
|
from data_classes import RepeatRule, Snooze, Metadata, Alarm
|
||||||
|
from logging_config import setup_logging
|
||||||
|
|
||||||
# Set up logging configuration
|
# Set up logging configuration
|
||||||
logging.basicConfig(
|
logger = setup_logging()
|
||||||
level=logging.DEBUG, # Set to DEBUG to show all log levels
|
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
||||||
handlers=[
|
|
||||||
logging.StreamHandler(), # Console handler
|
|
||||||
logging.FileHandler('alert_api.log') # File handler
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
logger = logging.getLogger('AlertApi')
|
|
||||||
|
|
||||||
class AlertApi(BaseHTTPRequestHandler):
|
class AlertApi(BaseHTTPRequestHandler):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.storage = AlarmStorage("data/alerts.json")
|
self.storage = AlarmStorage("data/alerts.json")
|
||||||
self.logger = logging.getLogger('AlertApi')
|
self.logger = logger
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def _send_response(self, status_code: int, data: Any = None, error: str = None) -> None:
|
def _send_response(self, status_code: int, data: Any = None, error: str = None) -> None:
|
||||||
|
102
alert_api/big_digits.py
Normal file
102
alert_api/big_digits.py
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
# Big digit patterns (15x7 size)
|
||||||
|
BIG_DIGITS = {
|
||||||
|
'0': [
|
||||||
|
" █████████ ",
|
||||||
|
" ███████████ ",
|
||||||
|
"███ ████",
|
||||||
|
"███ ████",
|
||||||
|
"███ ████",
|
||||||
|
" ███████████ ",
|
||||||
|
" █████████ "
|
||||||
|
],
|
||||||
|
'1': [
|
||||||
|
" ███ ",
|
||||||
|
" █████ ",
|
||||||
|
" ███ ",
|
||||||
|
" ███ ",
|
||||||
|
" ███ ",
|
||||||
|
" ███ ",
|
||||||
|
" ███████ "
|
||||||
|
],
|
||||||
|
'2': [
|
||||||
|
" ██████████ ",
|
||||||
|
"████████████",
|
||||||
|
" ████",
|
||||||
|
" ██████████ ",
|
||||||
|
"████ ",
|
||||||
|
"████████████",
|
||||||
|
" ██████████ "
|
||||||
|
],
|
||||||
|
'3': [
|
||||||
|
" ██████████ ",
|
||||||
|
"████████████",
|
||||||
|
" ████",
|
||||||
|
" ██████ ",
|
||||||
|
" ████",
|
||||||
|
"████████████",
|
||||||
|
" ██████████ "
|
||||||
|
],
|
||||||
|
'4': [
|
||||||
|
"███ ████",
|
||||||
|
"███ ████",
|
||||||
|
"███ ████",
|
||||||
|
"████████████",
|
||||||
|
" ████",
|
||||||
|
" ████",
|
||||||
|
" ████"
|
||||||
|
],
|
||||||
|
'5': [
|
||||||
|
"████████████",
|
||||||
|
"████████████",
|
||||||
|
"████ ",
|
||||||
|
"████████████",
|
||||||
|
" ████",
|
||||||
|
"████████████",
|
||||||
|
"████████████"
|
||||||
|
],
|
||||||
|
'6': [
|
||||||
|
" ██████████ ",
|
||||||
|
"████████████",
|
||||||
|
"████ ",
|
||||||
|
"████████████",
|
||||||
|
"████ ████",
|
||||||
|
"████████████",
|
||||||
|
" ██████████ "
|
||||||
|
],
|
||||||
|
'7': [
|
||||||
|
"████████████",
|
||||||
|
"████████████",
|
||||||
|
" ████ ",
|
||||||
|
" ████ ",
|
||||||
|
" ████ ",
|
||||||
|
"████ ",
|
||||||
|
"████ "
|
||||||
|
],
|
||||||
|
'8': [
|
||||||
|
" ██████████ ",
|
||||||
|
"████████████",
|
||||||
|
"████ ████",
|
||||||
|
" ██████████ ",
|
||||||
|
"████ ████",
|
||||||
|
"████████████",
|
||||||
|
" ██████████ "
|
||||||
|
],
|
||||||
|
'9': [
|
||||||
|
" ██████████ ",
|
||||||
|
"████████████",
|
||||||
|
"████ ████",
|
||||||
|
"████████████",
|
||||||
|
" ████",
|
||||||
|
"████████████",
|
||||||
|
" ██████████ "
|
||||||
|
],
|
||||||
|
':': [
|
||||||
|
" ",
|
||||||
|
" ████ ",
|
||||||
|
" ████ ",
|
||||||
|
" ",
|
||||||
|
" ████ ",
|
||||||
|
" ████ ",
|
||||||
|
" "
|
||||||
|
]
|
||||||
|
}
|
17
alert_api/logging_config.py
Normal file
17
alert_api/logging_config.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
def setup_logging(log_dir="logs", log_file="alarm_system.log", level=logging.DEBUG):
|
||||||
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
|
log_path = os.path.join(log_dir, log_file)
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
level=level,
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||||
|
handlers=[
|
||||||
|
logging.StreamHandler(),
|
||||||
|
logging.FileHandler(log_path, mode='a', encoding='utf-8')
|
||||||
|
]
|
||||||
|
)
|
||||||
|
logger = logging.getLogger("AlarmSystem")
|
||||||
|
return logger
|
@ -12,17 +12,11 @@ from multiprocessing import Queue
|
|||||||
from alarm_api import AlertApi, run as run_api
|
from alarm_api import AlertApi, run as run_api
|
||||||
from alarm_storage import AlarmStorage
|
from alarm_storage import AlarmStorage
|
||||||
from alarm_siren import AlarmSiren
|
from alarm_siren import AlarmSiren
|
||||||
|
from ncurses_ui import UI
|
||||||
|
from logging_config import setup_logging
|
||||||
|
|
||||||
# Set up logging
|
# Set up logging
|
||||||
logging.basicConfig(
|
logger = setup_logging()
|
||||||
level=logging.DEBUG,
|
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
||||||
handlers=[
|
|
||||||
logging.StreamHandler(),
|
|
||||||
logging.FileHandler('alarm_system.log')
|
|
||||||
]
|
|
||||||
)
|
|
||||||
logger = logging.getLogger('AlarmSystem')
|
|
||||||
|
|
||||||
class AlarmSystemManager:
|
class AlarmSystemManager:
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
@ -47,7 +41,6 @@ class AlarmSystemManager:
|
|||||||
self.siren = AlarmSiren()
|
self.siren = AlarmSiren()
|
||||||
self.storage = AlarmStorage(storage_path, siren=self.siren)
|
self.storage = AlarmStorage(storage_path, siren=self.siren)
|
||||||
|
|
||||||
|
|
||||||
# API server setup
|
# API server setup
|
||||||
self.api_port = api_port
|
self.api_port = api_port
|
||||||
self.api_server = None
|
self.api_server = None
|
||||||
@ -62,6 +55,9 @@ class AlarmSystemManager:
|
|||||||
# Alarm synchronization
|
# Alarm synchronization
|
||||||
self._sync_alarms()
|
self._sync_alarms()
|
||||||
|
|
||||||
|
# UI..
|
||||||
|
self.ui = UI(self)
|
||||||
|
|
||||||
def _setup_signal_handlers(self):
|
def _setup_signal_handlers(self):
|
||||||
"""Set up signal handlers for graceful shutdown"""
|
"""Set up signal handlers for graceful shutdown"""
|
||||||
signal.signal(signal.SIGINT, self._handle_shutdown)
|
signal.signal(signal.SIGINT, self._handle_shutdown)
|
||||||
@ -125,6 +121,9 @@ class AlarmSystemManager:
|
|||||||
# Start API server
|
# Start API server
|
||||||
self._start_api_server()
|
self._start_api_server()
|
||||||
|
|
||||||
|
# Start UI
|
||||||
|
ui_thread = self.ui.run()
|
||||||
|
|
||||||
# Log system startup
|
# Log system startup
|
||||||
logger.info("Alarm System started successfully")
|
logger.info("Alarm System started successfully")
|
||||||
|
|
||||||
|
230
alert_api/ncurses_ui.py
Normal file
230
alert_api/ncurses_ui.py
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
import curses
|
||||||
|
import time
|
||||||
|
from datetime import datetime, date, timedelta
|
||||||
|
import os
|
||||||
|
from big_digits import BIG_DIGITS
|
||||||
|
from ncurses_ui_draw import _draw_big_digit, _draw_main_clock, _draw_add_alarm, _draw_list_alarms, _draw_error
|
||||||
|
|
||||||
|
class UI:
|
||||||
|
def __init__(self, alarm_system_manager):
|
||||||
|
"""
|
||||||
|
Initialize the ncurses UI for the alarm system
|
||||||
|
|
||||||
|
Args:
|
||||||
|
alarm_system_manager (AlarmSystemManager): The main alarm system manager
|
||||||
|
"""
|
||||||
|
self.alarm_system = alarm_system_manager
|
||||||
|
self.stop_event = alarm_system_manager.stop_event
|
||||||
|
self.storage = alarm_system_manager.storage
|
||||||
|
|
||||||
|
# UI state variables
|
||||||
|
self.selected_menu = 0
|
||||||
|
self.new_alarm_name = "Alarm"
|
||||||
|
self.new_alarm_hour = datetime.now().hour
|
||||||
|
self.new_alarm_minute = datetime.now().minute
|
||||||
|
self.new_alarm_selected = 0
|
||||||
|
self.new_alarm_date = None
|
||||||
|
self.new_alarm_weekdays = []
|
||||||
|
self.new_alarm_enabled = True
|
||||||
|
self.weekday_names = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
||||||
|
self.alarms = []
|
||||||
|
self.error_message = None
|
||||||
|
self.error_timestamp = None
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""
|
||||||
|
Start the ncurses UI
|
||||||
|
"""
|
||||||
|
def ui_thread():
|
||||||
|
try:
|
||||||
|
curses.wrapper(self._main_loop)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error in UI thread: {e}")
|
||||||
|
finally:
|
||||||
|
self.stop_event.set()
|
||||||
|
|
||||||
|
import threading
|
||||||
|
ui_thread_obj = threading.Thread(target=ui_thread, daemon=True)
|
||||||
|
ui_thread_obj.start()
|
||||||
|
return ui_thread_obj
|
||||||
|
|
||||||
|
def _handle_add_alarm_input(self, key):
|
||||||
|
"""
|
||||||
|
Handle input for adding a new alarm
|
||||||
|
"""
|
||||||
|
if key == 27: # Escape
|
||||||
|
self.selected_menu = 0
|
||||||
|
return
|
||||||
|
|
||||||
|
if key == 10: # Enter
|
||||||
|
try:
|
||||||
|
# Prepare alarm data
|
||||||
|
alarm_data = {
|
||||||
|
"name": self.new_alarm_name,
|
||||||
|
"time": f"{self.new_alarm_hour:02d}:{self.new_alarm_minute:02d}:00",
|
||||||
|
"enabled": self.new_alarm_enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add repeat rule if applicable
|
||||||
|
if self.new_alarm_weekdays:
|
||||||
|
alarm_data["repeat_rule"] = {
|
||||||
|
"type": "weekly",
|
||||||
|
"days": self.new_alarm_weekdays
|
||||||
|
}
|
||||||
|
elif self.new_alarm_date:
|
||||||
|
alarm_data["repeat_rule"] = {
|
||||||
|
"type": "once",
|
||||||
|
"date": self.new_alarm_date.strftime("%Y-%m-%d")
|
||||||
|
}
|
||||||
|
|
||||||
|
# Save new alarm
|
||||||
|
self.storage.save_new_alert(alarm_data)
|
||||||
|
|
||||||
|
# Reset form
|
||||||
|
self.selected_menu = 0
|
||||||
|
self.new_alarm_hour = datetime.now().hour
|
||||||
|
self.new_alarm_minute = datetime.now().minute
|
||||||
|
self.new_alarm_date = None
|
||||||
|
self.new_alarm_weekdays = []
|
||||||
|
self.new_alarm_name = "Alarm"
|
||||||
|
self.new_alarm_enabled = True
|
||||||
|
except Exception as e:
|
||||||
|
# TODO: Show error on screen
|
||||||
|
print(f"Failed to save alarm: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
if key == curses.KEY_LEFT:
|
||||||
|
self.new_alarm_selected = (self.new_alarm_selected - 1) % 6
|
||||||
|
elif key == curses.KEY_RIGHT:
|
||||||
|
self.new_alarm_selected = (self.new_alarm_selected + 1) % 6
|
||||||
|
elif key == 32: # Space
|
||||||
|
if self.new_alarm_selected == 2: # Date
|
||||||
|
self.new_alarm_date = None
|
||||||
|
elif self.new_alarm_selected == 3: # Weekdays
|
||||||
|
current_day = len(self.new_alarm_weekdays)
|
||||||
|
if current_day < 7:
|
||||||
|
if current_day in self.new_alarm_weekdays:
|
||||||
|
self.new_alarm_weekdays.remove(current_day)
|
||||||
|
else:
|
||||||
|
self.new_alarm_weekdays.append(current_day)
|
||||||
|
self.new_alarm_weekdays.sort()
|
||||||
|
elif self.new_alarm_selected == 5: # Enabled toggle
|
||||||
|
self.new_alarm_enabled = not self.new_alarm_enabled
|
||||||
|
|
||||||
|
elif key == curses.KEY_UP:
|
||||||
|
if self.new_alarm_selected == 0:
|
||||||
|
self.new_alarm_hour = (self.new_alarm_hour + 1) % 24
|
||||||
|
elif self.new_alarm_selected == 1:
|
||||||
|
self.new_alarm_minute = (self.new_alarm_minute + 1) % 60
|
||||||
|
elif self.new_alarm_selected == 2:
|
||||||
|
if not self.new_alarm_date:
|
||||||
|
self.new_alarm_date = date.today()
|
||||||
|
else:
|
||||||
|
self.new_alarm_date += timedelta(days=1)
|
||||||
|
elif self.new_alarm_selected == 4: # Name
|
||||||
|
self.new_alarm_name += " "
|
||||||
|
|
||||||
|
elif key == curses.KEY_DOWN:
|
||||||
|
if self.new_alarm_selected == 0:
|
||||||
|
self.new_alarm_hour = (self.new_alarm_hour - 1) % 24
|
||||||
|
elif self.new_alarm_selected == 1:
|
||||||
|
self.new_alarm_minute = (self.new_alarm_minute - 1) % 60
|
||||||
|
elif self.new_alarm_selected == 2 and self.new_alarm_date:
|
||||||
|
self.new_alarm_date -= timedelta(days=1)
|
||||||
|
elif self.new_alarm_selected == 4 and len(self.new_alarm_name) > 1:
|
||||||
|
self.new_alarm_name = self.new_alarm_name.rstrip()[:-1]
|
||||||
|
|
||||||
|
def _handle_list_alarms_input(self, key):
|
||||||
|
"""
|
||||||
|
Handle input for the list alarms screen
|
||||||
|
"""
|
||||||
|
if key == 27: # Escape
|
||||||
|
self.selected_menu = 0
|
||||||
|
elif key == ord('d'):
|
||||||
|
# Delete last alarm if exists
|
||||||
|
if self.alarms:
|
||||||
|
last_alarm = self.alarms[-1]
|
||||||
|
try:
|
||||||
|
self.storage.remove_saved_alert(last_alarm['id'])
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to delete alarm: {e}")
|
||||||
|
|
||||||
|
def _show_error(self, message, duration=3):
|
||||||
|
"""Display an error message for a specified duration"""
|
||||||
|
self.error_message = message
|
||||||
|
self.error_timestamp = time.time()
|
||||||
|
|
||||||
|
def _clear_error_if_expired(self):
|
||||||
|
"""Clear error message if it has been displayed long enough"""
|
||||||
|
if self.error_message and self.error_timestamp:
|
||||||
|
if time.time() - self.error_timestamp > 3: # 3 seconds
|
||||||
|
self.error_message = None
|
||||||
|
self.error_timestamp = None
|
||||||
|
|
||||||
|
def _main_loop(self, stdscr):
|
||||||
|
"""
|
||||||
|
Main ncurses event loop
|
||||||
|
"""
|
||||||
|
# Initialize color pairs
|
||||||
|
curses.start_color()
|
||||||
|
curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_BLACK)
|
||||||
|
curses.init_pair(2, curses.COLOR_YELLOW, curses.COLOR_BLACK)
|
||||||
|
curses.init_pair(3, curses.COLOR_RED, curses.COLOR_BLACK)
|
||||||
|
curses.curs_set(0)
|
||||||
|
|
||||||
|
# Configure screen
|
||||||
|
stdscr.keypad(1)
|
||||||
|
stdscr.timeout(100)
|
||||||
|
|
||||||
|
while not self.stop_event.is_set():
|
||||||
|
# Clear the screen
|
||||||
|
stdscr.clear()
|
||||||
|
|
||||||
|
# Draw appropriate screen based on selected menu
|
||||||
|
if self.selected_menu == 0:
|
||||||
|
_draw_main_clock(stdscr)
|
||||||
|
elif self.selected_menu == 1:
|
||||||
|
_draw_add_alarm(stdscr, {
|
||||||
|
'new_alarm_selected': self.new_alarm_selected,
|
||||||
|
'new_alarm_name': self.new_alarm_name,
|
||||||
|
'new_alarm_hour': self.new_alarm_hour,
|
||||||
|
'new_alarm_minute': self.new_alarm_minute,
|
||||||
|
'new_alarm_enabled': self.new_alarm_enabled,
|
||||||
|
'new_alarm_date': self.new_alarm_date,
|
||||||
|
'new_alarm_weekdays': self.new_alarm_weekdays,
|
||||||
|
'weekday_names': self.weekday_names,
|
||||||
|
'new_alarm_snooze_enabled': getattr(self, 'new_alarm_snooze_enabled', False),
|
||||||
|
'new_alarm_snooze_duration': getattr(self, 'new_alarm_snooze_duration', 5),
|
||||||
|
'new_alarm_snooze_max_count': getattr(self, 'new_alarm_snooze_max_count', 3)
|
||||||
|
})
|
||||||
|
elif self.selected_menu == 2:
|
||||||
|
_draw_list_alarms(stdscr, {
|
||||||
|
'alarms': self.alarms,
|
||||||
|
'weekday_names': self.weekday_names
|
||||||
|
})
|
||||||
|
|
||||||
|
# Refresh the screen
|
||||||
|
stdscr.refresh()
|
||||||
|
|
||||||
|
# Small sleep to reduce CPU usage
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
# Handle input
|
||||||
|
key = stdscr.getch()
|
||||||
|
if key != -1:
|
||||||
|
# Menu navigation and input handling
|
||||||
|
if key == ord('q') or key == 27: # 'q' or Escape
|
||||||
|
break
|
||||||
|
elif key == ord('c'): # Clock/Home screen
|
||||||
|
self.selected_menu = 0
|
||||||
|
elif key == ord('a'): # Add Alarm
|
||||||
|
self.selected_menu = 1
|
||||||
|
elif key == ord('l'): # List Alarms
|
||||||
|
self.selected_menu = 2
|
||||||
|
self.alarms = self.storage.get_saved_alerts()
|
||||||
|
|
||||||
|
# Context-specific input handling
|
||||||
|
if self.selected_menu == 1:
|
||||||
|
self._handle_add_alarm_input(key)
|
||||||
|
elif self.selected_menu == 2:
|
||||||
|
self._handle_list_alarms_input(key)
|
184
alert_api/ncurses_ui_draw.py
Normal file
184
alert_api/ncurses_ui_draw.py
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
import curses
|
||||||
|
from big_digits import BIG_DIGITS
|
||||||
|
|
||||||
|
def _draw_error(stdscr, error_message):
|
||||||
|
"""Draw error message if present"""
|
||||||
|
if error_message:
|
||||||
|
height, width = stdscr.getmaxyx()
|
||||||
|
error_x = width // 2 - len(error_message) // 2
|
||||||
|
error_y = height - 4 # Show near bottom of screen
|
||||||
|
|
||||||
|
# Red color for errors
|
||||||
|
stdscr.attron(curses.color_pair(3))
|
||||||
|
stdscr.addstr(error_y, error_x, error_message)
|
||||||
|
stdscr.attroff(curses.color_pair(3))
|
||||||
|
|
||||||
|
def _draw_big_digit(stdscr, y, x, digit, big_digits):
|
||||||
|
"""
|
||||||
|
Draw a big digit using predefined patterns
|
||||||
|
"""
|
||||||
|
patterns = big_digits[digit]
|
||||||
|
for i, line in enumerate(patterns):
|
||||||
|
stdscr.addstr(y + i, x, line)
|
||||||
|
|
||||||
|
def _draw_big_time(stdscr, big_digits):
|
||||||
|
"""
|
||||||
|
Draw the time in big digits
|
||||||
|
"""
|
||||||
|
current_time = datetime.now()
|
||||||
|
time_str = current_time.strftime("%H:%M:%S")
|
||||||
|
|
||||||
|
# Get terminal dimensions
|
||||||
|
height, width = stdscr.getmaxyx()
|
||||||
|
|
||||||
|
# Calculate starting position to center the big clock
|
||||||
|
digit_width = 14 # Width of each digit pattern including spacing
|
||||||
|
total_width = digit_width * len(time_str)
|
||||||
|
start_x = (width - total_width) // 2
|
||||||
|
start_y = (height - 7) // 2 - 4 # Move up a bit to make room for date
|
||||||
|
|
||||||
|
# Color for the big time
|
||||||
|
stdscr.attron(curses.color_pair(1))
|
||||||
|
for i, digit in enumerate(time_str):
|
||||||
|
_draw_big_digit(stdscr, start_y, start_x + i * digit_width, digit, big_digits)
|
||||||
|
stdscr.attroff(curses.color_pair(1))
|
||||||
|
|
||||||
|
def _draw_main_clock(stdscr):
|
||||||
|
"""
|
||||||
|
Draw the main clock screen
|
||||||
|
"""
|
||||||
|
current_time = datetime.now()
|
||||||
|
time_str = current_time.strftime("%H:%M:%S")
|
||||||
|
date_str = current_time.strftime("%Y-%m-%d")
|
||||||
|
|
||||||
|
# Get terminal dimensions
|
||||||
|
height, width = stdscr.getmaxyx()
|
||||||
|
|
||||||
|
# Draw big time
|
||||||
|
# Note: You'll need to pass BIG_DIGITS from big_digits module when calling
|
||||||
|
_draw_big_time(stdscr, BIG_DIGITS)
|
||||||
|
|
||||||
|
# Draw date
|
||||||
|
date_x = width // 2 - len(date_str) // 2
|
||||||
|
date_y = height // 2 + 4 # Below the big clock
|
||||||
|
|
||||||
|
stdscr.attron(curses.color_pair(2))
|
||||||
|
stdscr.addstr(date_y, date_x, date_str)
|
||||||
|
stdscr.attroff(curses.color_pair(2))
|
||||||
|
|
||||||
|
# Draw menu options
|
||||||
|
menu_str = "A: Add Alarm L: List Alarms Q: Quit"
|
||||||
|
menu_x = width // 2 - len(menu_str) // 2
|
||||||
|
stdscr.addstr(height - 2, menu_x, menu_str)
|
||||||
|
|
||||||
|
def _draw_add_alarm(stdscr, context):
|
||||||
|
"""
|
||||||
|
Draw the add alarm screen
|
||||||
|
|
||||||
|
Args:
|
||||||
|
stdscr: The curses screen object
|
||||||
|
context: A dictionary containing UI state variables
|
||||||
|
"""
|
||||||
|
height, width = stdscr.getmaxyx()
|
||||||
|
|
||||||
|
form_y = height // 2 - 3
|
||||||
|
stdscr.addstr(form_y, width // 2 - 10, "Add New Alarm")
|
||||||
|
|
||||||
|
# Name input
|
||||||
|
if context['new_alarm_selected'] == 4:
|
||||||
|
stdscr.attron(curses.color_pair(2))
|
||||||
|
stdscr.addstr(form_y + 1, width // 2 - 10, f"Name: {context['new_alarm_name']}")
|
||||||
|
if context['new_alarm_selected'] == 4:
|
||||||
|
stdscr.attroff(curses.color_pair(2))
|
||||||
|
|
||||||
|
# Time selection
|
||||||
|
hour_str = f"{context['new_alarm_hour']:02d}"
|
||||||
|
minute_str = f"{context['new_alarm_minute']:02d}"
|
||||||
|
|
||||||
|
# Highlight selected field
|
||||||
|
if context['new_alarm_selected'] == 0:
|
||||||
|
stdscr.attron(curses.color_pair(2))
|
||||||
|
stdscr.addstr(form_y + 2, width // 2 - 2, hour_str)
|
||||||
|
if context['new_alarm_selected'] == 0:
|
||||||
|
stdscr.attroff(curses.color_pair(2))
|
||||||
|
|
||||||
|
stdscr.addstr(form_y + 2, width // 2, ":")
|
||||||
|
|
||||||
|
if context['new_alarm_selected'] == 1:
|
||||||
|
stdscr.attron(curses.color_pair(2))
|
||||||
|
stdscr.addstr(form_y + 2, width // 2 + 1, minute_str)
|
||||||
|
if context['new_alarm_selected'] == 1:
|
||||||
|
stdscr.attroff(curses.color_pair(2))
|
||||||
|
|
||||||
|
# Enabled/Disabled toggle
|
||||||
|
enabled_str = "Enabled" if context['new_alarm_enabled'] else "Disabled"
|
||||||
|
if context['new_alarm_selected'] == 5:
|
||||||
|
stdscr.attron(curses.color_pair(2))
|
||||||
|
stdscr.addstr(form_y + 4, width // 2 - len(enabled_str)//2, enabled_str)
|
||||||
|
if context['new_alarm_selected'] == 5:
|
||||||
|
stdscr.attroff(curses.color_pair(2))
|
||||||
|
|
||||||
|
# Date selection
|
||||||
|
date_str = "No specific date" if not context['new_alarm_date'] else context['new_alarm_date'].strftime("%Y-%m-%d")
|
||||||
|
if context['new_alarm_selected'] == 2:
|
||||||
|
stdscr.attron(curses.color_pair(2))
|
||||||
|
stdscr.addstr(form_y + 3, width // 2 - len(date_str) // 2, date_str)
|
||||||
|
if context['new_alarm_selected'] == 2:
|
||||||
|
stdscr.attroff(curses.color_pair(2))
|
||||||
|
|
||||||
|
# Weekday selection
|
||||||
|
weekday_str = "Repeat: " + " ".join(
|
||||||
|
context['weekday_names'][i] if i in context['new_alarm_weekdays'] else "___"
|
||||||
|
for i in range(7)
|
||||||
|
)
|
||||||
|
if context['new_alarm_selected'] == 3:
|
||||||
|
stdscr.attron(curses.color_pair(2))
|
||||||
|
stdscr.addstr(form_y + 5, width // 2 - len(weekday_str) // 2, weekday_str)
|
||||||
|
if context['new_alarm_selected'] == 3:
|
||||||
|
stdscr.attroff(curses.color_pair(2))
|
||||||
|
|
||||||
|
# Instructions
|
||||||
|
stdscr.addstr(height - 2, 2,
|
||||||
|
"↑↓: Change ←→: Switch Space: Toggle Enter: Save Esc: Cancel")
|
||||||
|
|
||||||
|
def _draw_list_alarms(stdscr, context):
|
||||||
|
"""
|
||||||
|
Draw the list of alarms screen
|
||||||
|
|
||||||
|
Args:
|
||||||
|
stdscr: The curses screen object
|
||||||
|
context: A dictionary containing UI state variables
|
||||||
|
"""
|
||||||
|
height, width = stdscr.getmaxyx()
|
||||||
|
|
||||||
|
# Header
|
||||||
|
stdscr.addstr(2, width // 2 - 5, "Alarms")
|
||||||
|
|
||||||
|
if not context['alarms']:
|
||||||
|
stdscr.addstr(4, width // 2 - 10, "No alarms set")
|
||||||
|
else:
|
||||||
|
for i, alarm in enumerate(context['alarms'][:height-6]):
|
||||||
|
# Format time and repeat information
|
||||||
|
time_str = alarm.get('time', 'Unknown')
|
||||||
|
|
||||||
|
# Format repeat info
|
||||||
|
repeat_info = ""
|
||||||
|
repeat_rule = alarm.get('repeat_rule', {})
|
||||||
|
if repeat_rule:
|
||||||
|
if repeat_rule.get('type') == 'weekly':
|
||||||
|
days = repeat_rule.get('days', [])
|
||||||
|
repeat_info = f" (Every {', '.join(context['weekday_names'][d] for d in days)})"
|
||||||
|
elif repeat_rule.get('type') == 'once' and repeat_rule.get('date'):
|
||||||
|
repeat_info = f" (On {repeat_rule['date']})"
|
||||||
|
|
||||||
|
# Status indicator
|
||||||
|
status = "✓" if alarm.get('enabled', True) else "✗"
|
||||||
|
display_str = f"{status} {time_str}{repeat_info}"
|
||||||
|
|
||||||
|
# Truncate if too long
|
||||||
|
display_str = display_str[:width-4]
|
||||||
|
|
||||||
|
stdscr.addstr(4 + i, 2, display_str)
|
||||||
|
|
||||||
|
stdscr.addstr(height - 2, 2, "D: Delete Enter: Edit Esc: Back")
|
Loading…
x
Reference in New Issue
Block a user