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)