2025-02-06 23:04:50 +02:00
|
|
|
import curses
|
|
|
|
from datetime import datetime
|
2025-02-09 22:31:24 +02:00
|
|
|
|
|
|
|
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:
|
2025-02-06 23:04:50 +02:00
|
|
|
# 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))
|
2025-02-09 22:31:24 +02:00
|
|
|
|
2025-02-06 23:04:50 +02:00
|
|
|
# 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))
|
|
|
|
|
2025-02-09 22:31:24 +02:00
|
|
|
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
|