import curses from datetime import datetime from big_digits import BIG_DIGITS def _init_colors(): """Initialize color pairs matching specification""" curses.start_color() curses.use_default_colors() # Green text on black background (primary color) curses.init_pair(1, curses.COLOR_GREEN, curses.COLOR_BLACK) # Highlight color (yellow) curses.init_pair(2, curses.COLOR_YELLOW, curses.COLOR_BLACK) # Error color (red) curses.init_pair(3, curses.COLOR_RED, curses.COLOR_BLACK) def _draw_error(stdscr, error_message): """Draw error message following specification""" height, width = stdscr.getmaxyx() # Truncate message if too long error_message = error_message[:width-4] error_x = max(0, width // 2 - len(error_message) // 2) error_y = height - 4 # Show near bottom of screen stdscr.attron(curses.color_pair(3) | curses.A_BOLD) stdscr.addstr(error_y, error_x, error_message) stdscr.attroff(curses.color_pair(3) | curses.A_BOLD) def _draw_active_alarms(stdscr, context): """Draw the active alarms screen""" height, width = stdscr.getmaxyx() active_alarms = context.get('active_alarms', {}) if not active_alarms: return # Get the first (or only) active alarm alarm_id = list(active_alarms.keys())[0] alarm_info = active_alarms[alarm_id] alarm_config = alarm_info['config'] # Draw alarm details stdscr.addstr(height // 2 - 2, width // 2 - 10, "ALARM TRIGGERED") stdscr.addstr(height // 2, width // 2 - len(alarm_config.get('name', 'Unnamed Alarm'))//2, alarm_config.get('name', 'Unnamed Alarm')) # Time info time_str = alarm_config.get('time', 'Unknown Time') stdscr.addstr(height // 2 + 2, width // 2 - len(time_str)//2, time_str) # Instructions stdscr.addstr(height - 2, width // 2 - 15, "S: Snooze D: Dismiss") def _draw_big_digit(stdscr, y, x, digit): """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_main_clock(stdscr, context=None): """Draw the main clock screen""" _init_colors() height, width = stdscr.getmaxyx() current_time = datetime.now() # Big time display time_str = current_time.strftime("%H:%M:%S") digit_width = 14 # Width of each digit pattern total_width = digit_width * len(time_str) start_x = (width - total_width) // 2 start_y = (height - 7) // 2 - 4 # Green color for 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) stdscr.attroff(curses.color_pair(1)) # Date display date_str = current_time.strftime("%Y-%m-%d") date_x = width // 2 - len(date_str) // 2 date_y = height // 2 + 4 stdscr.attron(curses.color_pair(2)) stdscr.addstr(date_y, date_x, date_str) stdscr.attroff(curses.color_pair(2)) # Menu options menu_str = "A: Add Alarm S: 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 following specification""" # Ensure context is a dictionary with default values if context is None: context = {} # Provide default values with more explicit checks context = { 'new_alarm_selected': context.get('new_alarm_selected', 0), 'new_alarm_name': context.get('new_alarm_name', 'New Alarm'), 'new_alarm_hour': context.get('new_alarm_hour', datetime.now().hour), 'new_alarm_minute': context.get('new_alarm_minute', datetime.now().minute), 'new_alarm_enabled': context.get('new_alarm_enabled', True), 'new_alarm_date': context.get('new_alarm_date') or None, 'new_alarm_weekdays': context.get('new_alarm_weekdays', []) or [], 'weekday_names': context.get('weekday_names', ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']) } _init_colors() height, width = stdscr.getmaxyx() form_y = height // 2 - 3 # Title stdscr.addstr(form_y - 1, width // 2 - 10, "Add New Alarm") # Name input name_str = str(context['new_alarm_name']) if context['new_alarm_selected'] == 4: stdscr.attron(curses.color_pair(2)) stdscr.addstr(form_y + 1, width // 2 - 10, f"Name: {name_str}") if context['new_alarm_selected'] == 4: stdscr.attroff(curses.color_pair(2)) # Time selection hour_str = f"{int(context['new_alarm_hour']):02d}" minute_str = f"{int(context['new_alarm_minute']):02d}" # Highlight time components 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 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 if context['new_alarm_date'] and hasattr(context['new_alarm_date'], 'strftime'): date_str = context['new_alarm_date'].strftime("%Y-%m-%d") else: date_str = 'No specific date' 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_names = context['weekday_names'] weekday_str = "Repeat: " + " ".join( weekday_names[i] if i in context['new_alarm_weekdays'] else "___" for i in range(len(weekday_names)) ) 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, "jk: Change hl: Switch Space: Toggle Enter: Save Esc: Cancel") def _draw_list_alarms(stdscr, context): """Draw the list of alarms screen""" _init_colors() height, width = stdscr.getmaxyx() # Header stdscr.addstr(2, width // 2 - 5, "Alarms") if not context.get('alarms'): stdscr.addstr(4, width // 2 - 10, "No alarms set") else: weekday_names = context.get('weekday_names', ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']) for i, alarm in enumerate(context['alarms'][:height-6]): # Format time 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(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} {alarm.get('name', 'Unnamed')}{repeat_info}" # Truncate if too long display_str = display_str[:width-4] stdscr.addstr(4 + i, 2, display_str) # Instructions stdscr.addstr(height - 2, 2, "D: Delete Enter: Edit Esc: Back")