Some nice UI upgrades.
This commit is contained in:
parent
c8bb1517c5
commit
3a3813e389
@ -10,13 +10,13 @@ BIG_DIGITS = {
|
|||||||
" █████████ "
|
" █████████ "
|
||||||
],
|
],
|
||||||
'1': [
|
'1': [
|
||||||
" ███ ",
|
" ████ ",
|
||||||
" █████ ",
|
" ██████ ",
|
||||||
" ███ ",
|
" ████ ",
|
||||||
" ███ ",
|
" ████ ",
|
||||||
" ███ ",
|
" ████ ",
|
||||||
" ███ ",
|
" ████ ",
|
||||||
" ███████ "
|
" █████████ "
|
||||||
],
|
],
|
||||||
'2': [
|
'2': [
|
||||||
" ██████████ ",
|
" ██████████ ",
|
||||||
|
@ -225,17 +225,33 @@ class UI:
|
|||||||
|
|
||||||
def _handle_list_alarms_input(self, key):
|
def _handle_list_alarms_input(self, key):
|
||||||
"""Handle input for alarm list view"""
|
"""Handle input for alarm list view"""
|
||||||
|
total_items = len(self.alarm_list) + 1 # +1 for "Add new alarm" option
|
||||||
|
|
||||||
if key == 27: # ESC
|
if key == 27: # ESC
|
||||||
self.current_view = 'CLOCK'
|
self.current_view = 'CLOCK'
|
||||||
|
elif key in [ord('j'), curses.KEY_DOWN]:
|
||||||
|
self.selected_item = (self.selected_item + 1) % total_items
|
||||||
|
elif key in [ord('k'), curses.KEY_UP]:
|
||||||
|
self.selected_item = (self.selected_item - 1) % total_items
|
||||||
elif key == ord('d'):
|
elif key == ord('d'):
|
||||||
# Delete last alarm
|
# Only delete if a real alarm is selected (not the "Add new" option)
|
||||||
if self.alarm_list:
|
if self.selected_item < len(self.alarm_list) and self.alarm_list:
|
||||||
last_alarm = self.alarm_list[-1]
|
|
||||||
try:
|
try:
|
||||||
self.storage.remove_saved_alert(last_alarm['id'])
|
alarm_to_delete = self.alarm_list[self.selected_item]
|
||||||
|
self.storage.remove_saved_alert(alarm_to_delete['id'])
|
||||||
self.alarm_list = self.storage.get_saved_alerts()
|
self.alarm_list = self.storage.get_saved_alerts()
|
||||||
|
# Adjust selected item if needed
|
||||||
|
if self.selected_item >= len(self.alarm_list):
|
||||||
|
self.selected_item = len(self.alarm_list)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._show_error(f"Failed to delete alarm: {e}")
|
self._show_error(f"Failed to delete alarm: {e}")
|
||||||
|
elif key in [ord('a'), 10]: # 'a' or Enter
|
||||||
|
if self.selected_item == len(self.alarm_list):
|
||||||
|
# "Add new alarm" option selected
|
||||||
|
self.current_view = 'ADD_ALARM'
|
||||||
|
else:
|
||||||
|
# TODO: Implement alarm editing
|
||||||
|
self._show_error("Alarm editing not implemented yet")
|
||||||
|
|
||||||
def _show_error(self, message, duration=30):
|
def _show_error(self, message, duration=30):
|
||||||
"""Display an error message"""
|
"""Display an error message"""
|
||||||
@ -271,7 +287,8 @@ class UI:
|
|||||||
elif self.current_view == 'LIST_ALARMS':
|
elif self.current_view == 'LIST_ALARMS':
|
||||||
_draw_list_alarms(stdscr, {
|
_draw_list_alarms(stdscr, {
|
||||||
'alarms': self.alarm_list or [],
|
'alarms': self.alarm_list or [],
|
||||||
'weekday_names': self.weekday_names
|
'weekday_names': self.weekday_names,
|
||||||
|
'selected_index': self.selected_item
|
||||||
})
|
})
|
||||||
elif self.current_view == 'ACTIVE_ALARMS':
|
elif self.current_view == 'ACTIVE_ALARMS':
|
||||||
# Draw active alarm view
|
# Draw active alarm view
|
||||||
|
@ -133,7 +133,7 @@ def _draw_main_clock(stdscr, context=None):
|
|||||||
stdscr.addstr(height - 2, menu_x, menu_str)
|
stdscr.addstr(height - 2, menu_x, menu_str)
|
||||||
|
|
||||||
def _draw_add_alarm(stdscr, context):
|
def _draw_add_alarm(stdscr, context):
|
||||||
"""Draw the add alarm screen following specification"""
|
"""Draw the add alarm screen"""
|
||||||
# Ensure context is a dictionary with default values
|
# Ensure context is a dictionary with default values
|
||||||
if context is None:
|
if context is None:
|
||||||
context = {}
|
context = {}
|
||||||
@ -152,92 +152,167 @@ def _draw_add_alarm(stdscr, context):
|
|||||||
|
|
||||||
_init_colors()
|
_init_colors()
|
||||||
height, width = stdscr.getmaxyx()
|
height, width = stdscr.getmaxyx()
|
||||||
form_y = height // 2 - 3
|
|
||||||
|
# Center the form vertically with good spacing
|
||||||
|
form_y = height // 2 - 6
|
||||||
|
|
||||||
# Title
|
# Title with green color and bold
|
||||||
stdscr.addstr(form_y - 1, width // 2 - 10, "Add New Alarm")
|
title = "Add New Alarm"
|
||||||
|
stdscr.attron(curses.color_pair(1) | curses.A_BOLD)
|
||||||
|
stdscr.addstr(form_y, width // 2 - len(title) // 2, title)
|
||||||
|
stdscr.attroff(curses.color_pair(1) | curses.A_BOLD)
|
||||||
|
|
||||||
# Name input
|
# Helper function to draw a labeled field
|
||||||
|
def draw_field(y, label, value, is_selected, center_offset=0):
|
||||||
|
label_str = f"{label}: "
|
||||||
|
total_width = len(label_str) + len(str(value))
|
||||||
|
x = width // 2 - total_width // 2 + center_offset
|
||||||
|
|
||||||
|
# Draw label in green
|
||||||
|
stdscr.attron(curses.color_pair(1))
|
||||||
|
stdscr.addstr(y, x, label_str)
|
||||||
|
stdscr.attroff(curses.color_pair(1))
|
||||||
|
|
||||||
|
# Draw value (highlighted if selected)
|
||||||
|
if is_selected:
|
||||||
|
stdscr.attron(curses.color_pair(2)) # Yellow for selected
|
||||||
|
stdscr.addstr(y, x + len(label_str), str(value))
|
||||||
|
stdscr.attroff(curses.color_pair(2))
|
||||||
|
else:
|
||||||
|
stdscr.attron(curses.color_pair(1)) # Green for normal
|
||||||
|
stdscr.addstr(y, x + len(label_str), str(value))
|
||||||
|
stdscr.attroff(curses.color_pair(1))
|
||||||
|
|
||||||
|
# Name field with proper spacing
|
||||||
name_str = str(context['new_alarm_name'])
|
name_str = str(context['new_alarm_name'])
|
||||||
if context['new_alarm_selected'] == 4:
|
draw_field(form_y + 2, "Name", name_str, 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
|
# Time selection with centered layout
|
||||||
|
time_y = form_y + 4
|
||||||
|
time_label = "Time: "
|
||||||
hour_str = f"{int(context['new_alarm_hour']):02d}"
|
hour_str = f"{int(context['new_alarm_hour']):02d}"
|
||||||
minute_str = f"{int(context['new_alarm_minute']):02d}"
|
minute_str = f"{int(context['new_alarm_minute']):02d}"
|
||||||
|
|
||||||
# Highlight time components
|
# Calculate center position for time
|
||||||
|
time_total_width = len(time_label) + 5 # 5 = HH:MM
|
||||||
|
time_x = width // 2 - time_total_width // 2
|
||||||
|
|
||||||
|
# Draw time label
|
||||||
|
stdscr.attron(curses.color_pair(1))
|
||||||
|
stdscr.addstr(time_y, time_x, time_label)
|
||||||
|
stdscr.attroff(curses.color_pair(1))
|
||||||
|
|
||||||
|
# Draw hour
|
||||||
if context['new_alarm_selected'] == 0:
|
if context['new_alarm_selected'] == 0:
|
||||||
stdscr.attron(curses.color_pair(2))
|
stdscr.attron(curses.color_pair(2))
|
||||||
stdscr.addstr(form_y + 2, width // 2 - 2, hour_str)
|
else:
|
||||||
|
stdscr.attron(curses.color_pair(1))
|
||||||
|
stdscr.addstr(time_y, time_x + len(time_label), hour_str)
|
||||||
if context['new_alarm_selected'] == 0:
|
if context['new_alarm_selected'] == 0:
|
||||||
stdscr.attroff(curses.color_pair(2))
|
stdscr.attroff(curses.color_pair(2))
|
||||||
|
else:
|
||||||
|
stdscr.attroff(curses.color_pair(1))
|
||||||
|
|
||||||
stdscr.addstr(form_y + 2, width // 2, ":")
|
# Draw colon
|
||||||
|
stdscr.attron(curses.color_pair(1))
|
||||||
|
stdscr.addstr(time_y, time_x + len(time_label) + 2, ":")
|
||||||
|
stdscr.attroff(curses.color_pair(1))
|
||||||
|
|
||||||
|
# Draw minute
|
||||||
if context['new_alarm_selected'] == 1:
|
if context['new_alarm_selected'] == 1:
|
||||||
stdscr.attron(curses.color_pair(2))
|
stdscr.attron(curses.color_pair(2))
|
||||||
stdscr.addstr(form_y + 2, width // 2 + 1, minute_str)
|
else:
|
||||||
|
stdscr.attron(curses.color_pair(1))
|
||||||
|
stdscr.addstr(time_y, time_x + len(time_label) + 3, minute_str)
|
||||||
if context['new_alarm_selected'] == 1:
|
if context['new_alarm_selected'] == 1:
|
||||||
stdscr.attroff(curses.color_pair(2))
|
stdscr.attroff(curses.color_pair(2))
|
||||||
|
else:
|
||||||
# Enabled toggle
|
stdscr.attroff(curses.color_pair(1))
|
||||||
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 selection
|
||||||
if context['new_alarm_date'] and hasattr(context['new_alarm_date'], 'strftime'):
|
if context['new_alarm_date'] and hasattr(context['new_alarm_date'], 'strftime'):
|
||||||
date_str = context['new_alarm_date'].strftime("%Y-%m-%d")
|
date_str = context['new_alarm_date'].strftime("%Y-%m-%d")
|
||||||
else:
|
else:
|
||||||
date_str = 'No specific date'
|
date_str = 'No specific date'
|
||||||
|
draw_field(form_y + 6, "Date", date_str, context['new_alarm_selected'] == 2)
|
||||||
|
|
||||||
if context['new_alarm_selected'] == 2:
|
# Weekday selection with improved visual style
|
||||||
stdscr.attron(curses.color_pair(2))
|
weekday_y = form_y + 8
|
||||||
stdscr.addstr(form_y + 3, width // 2 - len(date_str) // 2, date_str)
|
weekday_label = "Repeat: "
|
||||||
if context['new_alarm_selected'] == 2:
|
weekdays_str = " ".join(
|
||||||
stdscr.attroff(curses.color_pair(2))
|
f"[{context['weekday_names'][i]}]" if i in context['new_alarm_weekdays']
|
||||||
|
else f" {context['weekday_names'][i]} "
|
||||||
# Weekday selection
|
for i in range(len(context['weekday_names']))
|
||||||
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))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Center weekdays
|
||||||
|
total_width = len(weekday_label) + len(weekdays_str)
|
||||||
|
weekday_x = width // 2 - total_width // 2
|
||||||
|
|
||||||
|
# Draw weekday selection
|
||||||
|
stdscr.attron(curses.color_pair(1))
|
||||||
|
stdscr.addstr(weekday_y, weekday_x, weekday_label)
|
||||||
|
stdscr.attroff(curses.color_pair(1))
|
||||||
|
|
||||||
if context['new_alarm_selected'] == 3:
|
if context['new_alarm_selected'] == 3:
|
||||||
stdscr.attron(curses.color_pair(2))
|
stdscr.attron(curses.color_pair(2))
|
||||||
stdscr.addstr(form_y + 5, width // 2 - len(weekday_str) // 2, weekday_str)
|
else:
|
||||||
|
stdscr.attron(curses.color_pair(1))
|
||||||
|
stdscr.addstr(weekday_y, weekday_x + len(weekday_label), weekdays_str)
|
||||||
if context['new_alarm_selected'] == 3:
|
if context['new_alarm_selected'] == 3:
|
||||||
stdscr.attroff(curses.color_pair(2))
|
stdscr.attroff(curses.color_pair(2))
|
||||||
|
else:
|
||||||
|
stdscr.attroff(curses.color_pair(1))
|
||||||
|
|
||||||
# Instructions
|
# Enabled/Disabled toggle with visual indicator
|
||||||
stdscr.addstr(height - 2, 2,
|
status_y = form_y + 10
|
||||||
"jk: Change hl: Switch Space: Toggle Enter: Save Esc: Cancel")
|
enabled_str = "● Enabled" if context['new_alarm_enabled'] else "○ Disabled"
|
||||||
|
draw_field(status_y, "Status", enabled_str, context['new_alarm_selected'] == 5, -2)
|
||||||
|
|
||||||
|
# Instructions in green at the bottom
|
||||||
|
instructions = "j/k: Change h/l: Switch Space: Toggle Enter: Save Esc: Cancel"
|
||||||
|
stdscr.attron(curses.color_pair(1))
|
||||||
|
stdscr.addstr(height - 2, width // 2 - len(instructions) // 2, instructions)
|
||||||
|
stdscr.attroff(curses.color_pair(1))
|
||||||
|
|
||||||
def _draw_list_alarms(stdscr, context):
|
def _draw_list_alarms(stdscr, context):
|
||||||
"""Draw the list of alarms screen"""
|
"""Draw the list of alarms screen"""
|
||||||
_init_colors()
|
_init_colors()
|
||||||
height, width = stdscr.getmaxyx()
|
height, width = stdscr.getmaxyx()
|
||||||
|
|
||||||
|
# Get required data from context
|
||||||
|
alarms = context.get('alarms', [])
|
||||||
|
selected_index = context.get('selected_index', 0)
|
||||||
|
weekday_names = context.get('weekday_names', ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'])
|
||||||
|
|
||||||
|
# Calculate visible range for scrolling
|
||||||
|
max_visible_items = height - 8 # Leave space for header and footer
|
||||||
|
total_items = len(alarms) + 1 # +1 for "Add new alarm" option
|
||||||
|
|
||||||
|
# Calculate scroll position
|
||||||
|
start_idx = max(0, min(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)
|
||||||
|
|
||||||
# Header
|
# Header
|
||||||
stdscr.addstr(2, width // 2 - 5, "Alarms")
|
header_text = "Alarms"
|
||||||
|
stdscr.attron(curses.color_pair(1) | curses.A_BOLD)
|
||||||
if not context.get('alarms'):
|
stdscr.addstr(2, width // 2 - len(header_text) // 2, header_text)
|
||||||
stdscr.addstr(4, width // 2 - 10, "No alarms set")
|
stdscr.attroff(curses.color_pair(1) | curses.A_BOLD)
|
||||||
else:
|
|
||||||
weekday_names = context.get('weekday_names', ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'])
|
# Draw alarms
|
||||||
|
for i in range(start_idx, end_idx):
|
||||||
for i, alarm in enumerate(context['alarms'][:height-6]):
|
y_pos = 4 + (i - start_idx)
|
||||||
|
|
||||||
|
if i == len(alarms): # "Add new alarm" option
|
||||||
|
display_str = "Add new alarm..."
|
||||||
|
else:
|
||||||
|
alarm = alarms[i]
|
||||||
# Format time
|
# Format time
|
||||||
time_str = alarm.get('time', 'Unknown')
|
time_str = alarm.get('time', 'Unknown')
|
||||||
|
|
||||||
# Format repeat info
|
# Format repeat info
|
||||||
repeat_info = ""
|
repeat_info = ""
|
||||||
repeat_rule = alarm.get('repeat_rule', {})
|
repeat_rule = alarm.get('repeat_rule', {})
|
||||||
@ -247,15 +322,38 @@ def _draw_list_alarms(stdscr, context):
|
|||||||
repeat_info = f" (Every {', '.join(weekday_names[d] for d in days)})"
|
repeat_info = f" (Every {', '.join(weekday_names[d] for d in days)})"
|
||||||
elif repeat_rule.get('type') == 'once' and repeat_rule.get('date'):
|
elif repeat_rule.get('type') == 'once' and repeat_rule.get('date'):
|
||||||
repeat_info = f" (On {repeat_rule['date']})"
|
repeat_info = f" (On {repeat_rule['date']})"
|
||||||
|
|
||||||
# Status indicator
|
# Status indicator (in green)
|
||||||
status = "✓" if alarm.get('enabled', True) else "✗"
|
status = "✓" if alarm.get('enabled', True) else "✗"
|
||||||
display_str = f"{status} {time_str} {alarm.get('name', 'Unnamed')}{repeat_info}"
|
display_str = f"{status} {time_str} {alarm.get('name', 'Unnamed')}{repeat_info}"
|
||||||
|
|
||||||
# Truncate if too long
|
# Truncate if too long (leaving space for selection brackets)
|
||||||
display_str = display_str[:width-4]
|
max_length = width - 6
|
||||||
|
if len(display_str) > max_length:
|
||||||
stdscr.addstr(4 + i, 2, display_str)
|
display_str = display_str[:max_length-3] + "..."
|
||||||
|
|
||||||
|
# Center the item
|
||||||
|
x_pos = width // 2 - len(display_str) // 2
|
||||||
|
|
||||||
|
# Highlight selected item
|
||||||
|
if i == selected_index:
|
||||||
|
# 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))
|
||||||
|
|
||||||
# Instructions
|
# Instructions
|
||||||
stdscr.addstr(height - 2, 2, "D: Delete Enter: Edit Esc: Back")
|
instructions = "j/k: Move d: Delete a: Add/Edit Esc: Back"
|
||||||
|
stdscr.attron(curses.color_pair(1))
|
||||||
|
stdscr.addstr(height - 2, width // 2 - len(instructions) // 2, instructions)
|
||||||
|
stdscr.attroff(curses.color_pair(1))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user