import curses from datetime import datetime class ListAlarmsView: def __init__(self, storage): """Initialize the list alarms view""" self.storage = storage self.weekday_names = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] self.selected_index = 0 self.alarms = [] def reset_state(self): """Reset the view state""" self.selected_index = 0 self.alarms = self.storage.get_saved_alerts() def update_alarms(self, alarms): """Update the list of alarms to display.""" self.alarms = alarms self.selected_index = 0 def get_selected_alarm(self): """Get the currently selected alarm.""" if 0 <= self.selected_index < len(self.alarms): return self.alarms[self.selected_index] return None def draw(self, stdscr): """Draw the list of alarms screen""" height, width = stdscr.getmaxyx() self._draw_header(stdscr, width) visible_range = self._calculate_visible_range(height) self._draw_alarm_list(stdscr, height, width, visible_range) self._draw_instructions(stdscr, height, width) def handle_input(self, key): """Handle user input and return the next view name or None to stay""" total_items = len(self.alarms) + 1 # +1 for "Add new alarm" option if key == 27: # ESC return 'CLOCK' elif key in [ord('j'), curses.KEY_DOWN]: self.selected_index = (self.selected_index + 1) % total_items elif key in [ord('k'), curses.KEY_UP]: self.selected_index = (self.selected_index - 1) % total_items elif key == ord('d'): return self._handle_delete() elif key in [ord('a'), 10]: # 'a' or Enter return self._handle_add_edit() return None def _calculate_visible_range(self, height): """Calculate the visible range for scrolling""" max_visible_items = height - 8 # Space for header and footer total_items = len(self.alarms) + 1 # +1 for "Add new alarm" option # Calculate scroll position start_idx = max(0, min(self.selected_index - max_visible_items // 2, total_items - max_visible_items)) if start_idx < 0: start_idx = 0 end_idx = min(start_idx + max_visible_items, total_items) return (start_idx, end_idx) def _draw_header(self, stdscr, width): """Draw the header text""" header_text = "Alarms" stdscr.attron(curses.color_pair(1) | curses.A_BOLD) stdscr.addstr(2, self._center_x(width, header_text), header_text) stdscr.attroff(curses.color_pair(1) | curses.A_BOLD) def _draw_alarm_list(self, stdscr, height, width, visible_range): """Draw the list of alarms""" start_idx, end_idx = visible_range for i in range(start_idx, end_idx): y_pos = 4 + (i - start_idx) display_str = self._format_alarm_display(i) # Truncate if too long max_length = width - 6 if len(display_str) > max_length: display_str = display_str[:max_length-3] + "..." x_pos = self._center_x(width, display_str) self._draw_alarm_item(stdscr, y_pos, x_pos, display_str, i == self.selected_index) def _format_alarm_display(self, index): """Format the display string for an alarm""" if index == len(self.alarms): return "Add new alarm..." alarm = self.alarms[index] time_str = alarm.get('time', 'Unknown') repeat_info = self._format_repeat_info(alarm) status = "✓" if alarm.get('enabled', True) else "✗" return f"{status} {time_str} {alarm.get('name', 'Unnamed')}{repeat_info}" def _format_repeat_info(self, alarm): """Format the repeat information for an alarm""" repeat_rule = alarm.get('repeat_rule', {}) if not repeat_rule: return "" if repeat_rule.get('type') == 'weekly': days = repeat_rule.get('days', []) return f" (Every {', '.join(self.weekday_names[d] for d in days)})" elif repeat_rule.get('type') == 'once' and repeat_rule.get('date'): return f" (On {repeat_rule['date']})" return "" def _draw_alarm_item(self, stdscr, y_pos, x_pos, display_str, is_selected): """Draw a single alarm item""" if is_selected: # Draw selection brackets in green stdscr.attron(curses.color_pair(1)) stdscr.addstr(y_pos, x_pos - 2, "[ ") stdscr.addstr(y_pos, x_pos + len(display_str), " ]") stdscr.attroff(curses.color_pair(1)) # Draw text in yellow (highlighted) stdscr.attron(curses.color_pair(2)) stdscr.addstr(y_pos, x_pos, display_str) stdscr.attroff(curses.color_pair(2)) else: # Draw normal items in green stdscr.attron(curses.color_pair(1)) stdscr.addstr(y_pos, x_pos, display_str) stdscr.attroff(curses.color_pair(1)) def _draw_instructions(self, stdscr, height, width): """Draw the instructions at the bottom of the screen""" instructions = "j/k: Move d: Delete a: Add/Edit Esc: Back" stdscr.attron(curses.color_pair(1)) stdscr.addstr(height - 2, self._center_x(width, instructions), instructions) stdscr.attroff(curses.color_pair(1)) def _center_x(self, width, text): """Calculate x coordinate to center text""" return width // 2 - len(text) // 2 def _handle_delete(self): """Handle alarm deletion""" if self.selected_index < len(self.alarms): try: alarm_to_delete = self.alarms[self.selected_index] self.storage.remove_saved_alert(alarm_to_delete['id']) self.alarms = self.storage.get_saved_alerts() # Adjust selected item if needed if self.selected_index >= len(self.alarms): self.selected_index = len(self.alarms) except Exception as e: # You might want to add error handling here pass return None def _handle_add_edit(self): """Handle add/edit action""" if self.selected_index == len(self.alarms): # "Add new alarm" option selected return 'ADD_ALARM' else: # Edit existing alarm selected_alarm = self.alarms[self.selected_index] return ('ADD_ALARM', selected_alarm) # Pass alarm to AddAlarmView