We have sound! And mostly working ncurses ui!
This commit is contained in:
parent
4e1c838eaa
commit
a48976a762
@ -7,16 +7,10 @@ import logging
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Optional, Dict, Any
|
from typing import Optional, Dict, Any
|
||||||
|
|
||||||
|
from logging_config import setup_logging
|
||||||
|
|
||||||
# Set up logging
|
# Set up logging
|
||||||
logging.basicConfig(
|
logger = setup_logging()
|
||||||
level=logging.DEBUG,
|
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
||||||
handlers=[
|
|
||||||
logging.StreamHandler(),
|
|
||||||
logging.FileHandler('alarm_siren.log')
|
|
||||||
]
|
|
||||||
)
|
|
||||||
logger = logging.getLogger('AlarmSiren')
|
|
||||||
|
|
||||||
class AlarmSiren:
|
class AlarmSiren:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -45,26 +39,48 @@ class AlarmSiren:
|
|||||||
alarm_time = datetime.strptime(alarm_config['time'], "%H:%M:%S").time()
|
alarm_time = datetime.strptime(alarm_config['time'], "%H:%M:%S").time()
|
||||||
|
|
||||||
# Determine the next trigger
|
# Determine the next trigger
|
||||||
if alarm_config['repeat_rule']['type'] == 'once':
|
repeat_rule = alarm_config['repeat_rule']
|
||||||
|
|
||||||
|
# Determine repeat rule type and details
|
||||||
|
if isinstance(repeat_rule, dict):
|
||||||
|
repeat_type = repeat_rule.get('type')
|
||||||
|
repeat_days = repeat_rule.get('days_of_week', [])
|
||||||
|
repeat_at = repeat_rule.get('at')
|
||||||
|
else:
|
||||||
|
# Assume it's an object-like structure with attributes
|
||||||
|
repeat_type = getattr(repeat_rule, 'type', None)
|
||||||
|
repeat_days = getattr(repeat_rule, 'days_of_week', [])
|
||||||
|
repeat_at = getattr(repeat_rule, 'at', None)
|
||||||
|
|
||||||
|
# Sanity check
|
||||||
|
if repeat_type is None:
|
||||||
|
logger.error(f"Invalid repeat rule configuration: {repeat_rule}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
if repeat_type == 'once':
|
||||||
# For one-time alarm, check the specific date
|
# For one-time alarm, check the specific date
|
||||||
try:
|
try:
|
||||||
specific_date = datetime.strptime(alarm_config['repeat_rule']['at'], "%d.%m.%Y")
|
# If 'at' is None, use current date
|
||||||
return datetime.combine(specific_date.date(), alarm_time)
|
if repeat_at is None:
|
||||||
except (KeyError, ValueError):
|
specific_date = now.date()
|
||||||
|
else:
|
||||||
|
specific_date = datetime.strptime(repeat_at, "%d.%m.%Y").date()
|
||||||
|
return datetime.combine(specific_date, alarm_time)
|
||||||
|
except ValueError:
|
||||||
logger.error("Invalid one-time alarm configuration")
|
logger.error("Invalid one-time alarm configuration")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
elif alarm_config['repeat_rule']['type'] == 'daily':
|
elif repeat_type == 'daily':
|
||||||
# Daily alarm - trigger today or tomorrow
|
# Daily alarm - trigger today or tomorrow
|
||||||
next_trigger = datetime.combine(now.date(), alarm_time)
|
next_trigger = datetime.combine(now.date(), alarm_time)
|
||||||
if current_time < alarm_time:
|
if current_time < alarm_time:
|
||||||
return next_trigger
|
return next_trigger
|
||||||
return next_trigger + timedelta(days=1)
|
return next_trigger + timedelta(days=1)
|
||||||
|
|
||||||
elif alarm_config['repeat_rule']['type'] == 'weekly':
|
elif repeat_type == 'weekly':
|
||||||
# Weekly alarm - check configured days
|
# Weekly alarm - check configured days
|
||||||
today = now.strftime("%A").lower()
|
today = now.strftime("%A").lower()
|
||||||
configured_days = [day.lower() for day in alarm_config['repeat_rule'].get('days_of_week', [])]
|
configured_days = [day.lower() for day in repeat_days]
|
||||||
|
|
||||||
if today in configured_days and current_time < alarm_time:
|
if today in configured_days and current_time < alarm_time:
|
||||||
return datetime.combine(now.date(), alarm_time)
|
return datetime.combine(now.date(), alarm_time)
|
||||||
@ -158,26 +174,42 @@ class AlarmSiren:
|
|||||||
|
|
||||||
def snooze_alarm(self, alarm_id: int):
|
def snooze_alarm(self, alarm_id: int):
|
||||||
"""Snooze a specific alarm"""
|
"""Snooze a specific alarm"""
|
||||||
if alarm_id in self.active_alarms:
|
if alarm_id not in self.active_alarms:
|
||||||
alarm_config = self.active_alarms[alarm_id]['config']
|
logger.warning(f"Cannot snooze alarm {alarm_id} - not found in active alarms")
|
||||||
snooze_config = alarm_config.get('snooze', {'enabled': True, 'duration': 10, 'max_count': 3})
|
return False
|
||||||
|
|
||||||
if (snooze_config['enabled'] and
|
alarm_info = self.active_alarms[alarm_id]
|
||||||
self.active_alarms[alarm_id]['snooze_count'] < snooze_config['max_count']):
|
alarm_config = alarm_info['config']
|
||||||
|
|
||||||
|
# Default snooze configuration if not provided
|
||||||
|
snooze_config = alarm_config.get('snooze', {
|
||||||
|
'enabled': True,
|
||||||
|
'duration': 5, # Default 5 minutes
|
||||||
|
'max_count': 3 # Default max 3 snoozes
|
||||||
|
})
|
||||||
|
|
||||||
|
# Ensure all required keys exist with default values
|
||||||
|
snooze_config.setdefault('enabled', True)
|
||||||
|
snooze_config.setdefault('duration', 5)
|
||||||
|
snooze_config.setdefault('max_count', 3)
|
||||||
|
|
||||||
|
# Check if snoozing is allowed
|
||||||
|
if not snooze_config['enabled']:
|
||||||
|
logger.warning(f"Snooze not enabled for alarm {alarm_id}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Check snooze count
|
||||||
|
current_snooze_count = alarm_info.get('snooze_count', 0)
|
||||||
|
if current_snooze_count >= snooze_config['max_count']:
|
||||||
|
logger.warning(f"Maximum snooze count reached for alarm {alarm_id}")
|
||||||
|
return False
|
||||||
|
|
||||||
# Increment snooze count
|
# Increment snooze count
|
||||||
self.active_alarms[alarm_id]['snooze_count'] += 1
|
alarm_info['snooze_count'] = current_snooze_count + 1
|
||||||
|
|
||||||
# Set next trigger time
|
# Set next trigger time
|
||||||
snooze_duration = snooze_config.get('duration', 10)
|
snooze_duration = snooze_config['duration']
|
||||||
self.active_alarms[alarm_id]['trigger_time'] = datetime.now() + timedelta(minutes=snooze_duration)
|
alarm_info['trigger_time'] = datetime.now() + timedelta(minutes=snooze_duration)
|
||||||
|
|
||||||
logger.info(f"Snoozed alarm {alarm_id} for {snooze_duration} minutes")
|
logger.info(f"Snoozed alarm {alarm_id} for {snooze_duration} minutes. Snooze count: {alarm_info['snooze_count']}")
|
||||||
else:
|
return True
|
||||||
logger.warning(f"Cannot snooze alarm {alarm_id} - max snooze count reached")
|
|
||||||
|
|
||||||
def dismiss_alarm(self, alarm_id: int):
|
|
||||||
"""Dismiss a specific alarm"""
|
|
||||||
if alarm_id in self.active_alarms:
|
|
||||||
logger.info(f"Dismissed alarm {alarm_id}")
|
|
||||||
del self.active_alarms[alarm_id]
|
|
||||||
|
@ -1,225 +1,176 @@
|
|||||||
import curses
|
import curses
|
||||||
import time
|
import time
|
||||||
from datetime import datetime, date, timedelta
|
from datetime import datetime, date, timedelta
|
||||||
import os
|
import threading
|
||||||
from big_digits import BIG_DIGITS
|
import logging
|
||||||
from ncurses_ui_draw import _draw_big_digit, _draw_main_clock, _draw_add_alarm, _draw_list_alarms, _draw_error
|
|
||||||
from logging_config import setup_logging
|
|
||||||
|
|
||||||
# Set up logging
|
# Import drawing methods from the new module
|
||||||
logger = setup_logging()
|
from ncurses_ui_draw import (
|
||||||
|
_draw_main_clock,
|
||||||
|
_draw_add_alarm,
|
||||||
|
_draw_list_alarms,
|
||||||
|
_draw_error
|
||||||
|
)
|
||||||
|
|
||||||
|
# class AlarmClockUI:
|
||||||
class UI:
|
class UI:
|
||||||
def __init__(self, alarm_system_manager):
|
def __init__(self, alarm_system_manager):
|
||||||
"""
|
# UI State Management
|
||||||
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.alarm_system = alarm_system_manager
|
||||||
self.stop_event = alarm_system_manager.stop_event
|
self.stop_event = alarm_system_manager.stop_event
|
||||||
self.storage = alarm_system_manager.storage
|
self.storage = alarm_system_manager.storage
|
||||||
|
|
||||||
# UI state variables
|
# Logging
|
||||||
self.selected_menu = 0
|
self.logger = logging.getLogger(__name__)
|
||||||
self.new_alarm_name = "Alarm"
|
|
||||||
self.editing_name = " "
|
# UI State
|
||||||
self.new_alarm_hour = datetime.now().hour
|
self.reset_ui_state()
|
||||||
self.new_alarm_minute = datetime.now().minute
|
|
||||||
self.new_alarm_selected = 4
|
def reset_ui_state(self):
|
||||||
self.new_alarm_date = None
|
"""Reset all UI state variables to their initial values"""
|
||||||
self.new_alarm_weekdays = []
|
# Menu states
|
||||||
self.new_alarm_enabled = True
|
self.current_view = 'CLOCK'
|
||||||
self.weekday_names = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
self.selected_item = 0
|
||||||
self.alarms = []
|
|
||||||
|
# Alarm Creation State
|
||||||
|
self.alarm_draft = {
|
||||||
|
'name': 'New Alarm',
|
||||||
|
'hour': datetime.now().hour,
|
||||||
|
'minute': datetime.now().minute,
|
||||||
|
'enabled': True,
|
||||||
|
'date': None,
|
||||||
|
'weekdays': [],
|
||||||
|
'editing_name': False,
|
||||||
|
'temp_name': ''
|
||||||
|
}
|
||||||
|
|
||||||
|
# Error handling
|
||||||
self.error_message = None
|
self.error_message = None
|
||||||
self.error_timestamp = None
|
self.error_timestamp = None
|
||||||
|
|
||||||
|
# Alarm list
|
||||||
|
self.alarm_list = []
|
||||||
|
|
||||||
|
# Weekday names (to match specification)
|
||||||
|
self.weekday_names = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""
|
"""Start the ncurses UI in a separate thread"""
|
||||||
Start the ncurses UI
|
|
||||||
"""
|
|
||||||
def ui_thread():
|
def ui_thread():
|
||||||
try:
|
try:
|
||||||
curses.wrapper(self._main_loop)
|
curses.wrapper(self._main_loop)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in UI thread: {e}")
|
self.logger.error(f"UI Thread Error: {e}")
|
||||||
print(f"Error in UI thread: {e}")
|
|
||||||
finally:
|
finally:
|
||||||
self.stop_event.set()
|
self.stop_event.set()
|
||||||
|
|
||||||
import threading
|
|
||||||
ui_thread_obj = threading.Thread(target=ui_thread, daemon=True)
|
ui_thread_obj = threading.Thread(target=ui_thread, daemon=True)
|
||||||
ui_thread_obj.start()
|
ui_thread_obj.start()
|
||||||
return ui_thread_obj
|
return ui_thread_obj
|
||||||
|
|
||||||
|
def _handle_clock_input(self, key):
|
||||||
|
"""Handle input on the clock view"""
|
||||||
|
if key == ord('a'):
|
||||||
|
self.current_view = 'ADD_ALARM'
|
||||||
|
elif key == ord('s'):
|
||||||
|
self.current_view = 'LIST_ALARMS'
|
||||||
|
self.alarm_list = self.storage.get_saved_alerts()
|
||||||
|
|
||||||
def _handle_add_alarm_input(self, key):
|
def _handle_add_alarm_input(self, key):
|
||||||
try:
|
"""Comprehensive input handling for alarm creation"""
|
||||||
if key == 27: # Escape
|
alarm = self.alarm_draft
|
||||||
# If in name editing, exit name editing
|
|
||||||
if self.editing_name:
|
# Escape key handling
|
||||||
self.new_alarm_name = self.temp_alarm_name
|
if key == 27: # ESC
|
||||||
self.editing_name = False
|
if alarm['editing_name']:
|
||||||
return
|
# Cancel name editing
|
||||||
|
alarm['name'] = alarm['temp_name']
|
||||||
# Otherwise return to main clock
|
alarm['editing_name'] = False
|
||||||
self.selected_menu = 0
|
else:
|
||||||
return
|
# Return to clock view
|
||||||
|
self.current_view = 'CLOCK'
|
||||||
if key == 10: # Enter
|
|
||||||
if self.editing_name:
|
|
||||||
# Exit name editing mode
|
|
||||||
self.editing_name = False
|
|
||||||
# Move focus to time selection
|
|
||||||
self.new_alarm_selected = 0
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Enter key handling
|
||||||
|
if key == 10: # ENTER
|
||||||
|
if alarm['editing_name']:
|
||||||
|
# Finish name editing
|
||||||
|
alarm['editing_name'] = False
|
||||||
|
self.selected_item = 0
|
||||||
|
else:
|
||||||
|
# Save alarm
|
||||||
try:
|
try:
|
||||||
alarm_data = {
|
alarm_data = {
|
||||||
"name": self.new_alarm_name,
|
"name": alarm['name'],
|
||||||
"time": f"{self.new_alarm_hour:02d}:{self.new_alarm_minute:02d}:00",
|
"time": f"{alarm['hour']:02d}:{alarm['minute']:02d}:00",
|
||||||
"enabled": self.new_alarm_enabled,
|
"enabled": alarm['enabled'],
|
||||||
"repeat_rule": {
|
"repeat_rule": {
|
||||||
"type": "weekly" if self.new_alarm_weekdays else "once",
|
"type": "weekly" if alarm['weekdays'] else "once",
|
||||||
"days": self.new_alarm_weekdays if self.new_alarm_weekdays else [],
|
"days_of_week": [self.weekday_names[day].lower() for day in alarm['weekdays']],
|
||||||
"date": self.new_alarm_date.strftime("%Y-%m-%d") if self.new_alarm_date else date.today().strftime("%Y-%m-%d")
|
"at": alarm['date'].strftime("%d.%m.%Y") if alarm['date'] else None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.storage.save_new_alert(alarm_data)
|
self.storage.save_new_alert(alarm_data)
|
||||||
self.selected_menu = 0
|
self.current_view = 'CLOCK'
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._show_error(str(e))
|
self._show_error(str(e))
|
||||||
return
|
return
|
||||||
|
|
||||||
# Numeric input for time when on time selection
|
# Navigation and editing
|
||||||
if self.new_alarm_selected in [0, 1] and not self.editing_name:
|
if not alarm['editing_name']:
|
||||||
if 48 <= key <= 57: # 0-9 keys
|
|
||||||
current_digit = int(chr(key))
|
|
||||||
if self.new_alarm_selected == 0: # Hour
|
|
||||||
self.new_alarm_hour = current_digit if self.new_alarm_hour < 10 else (self.new_alarm_hour % 10 * 10 + current_digit) % 24
|
|
||||||
else: # Minute
|
|
||||||
self.new_alarm_minute = current_digit if self.new_alarm_minute < 10 else (self.new_alarm_minute % 10 * 10 + current_digit) % 60
|
|
||||||
return
|
|
||||||
|
|
||||||
# Use hjkl for navigation and selection
|
|
||||||
if key in [ord('h'), curses.KEY_LEFT]:
|
if key in [ord('h'), curses.KEY_LEFT]:
|
||||||
self.new_alarm_selected = (self.new_alarm_selected - 1) % 6
|
self.selected_item = (self.selected_item - 1) % 6
|
||||||
elif key in [ord('l'), curses.KEY_RIGHT]:
|
elif key in [ord('l'), curses.KEY_RIGHT]:
|
||||||
self.new_alarm_selected = (self.new_alarm_selected + 1) % 6
|
self.selected_item = (self.selected_item + 1) % 6
|
||||||
elif key in [ord('k'), 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 == 3 and len(self.new_alarm_weekdays) < 7:
|
|
||||||
# Add whole groups of days
|
|
||||||
if key == ord('k'):
|
|
||||||
# Options: M-F (0-4), Weekends (5-6), All days
|
|
||||||
if not self.new_alarm_weekdays:
|
|
||||||
self.new_alarm_weekdays = list(range(5)) # M-F
|
|
||||||
elif self.new_alarm_weekdays == list(range(5)):
|
|
||||||
self.new_alarm_weekdays = [5, 6] # Weekends
|
|
||||||
elif self.new_alarm_weekdays == [5, 6]:
|
|
||||||
self.new_alarm_weekdays = list(range(7)) # All days
|
|
||||||
else:
|
|
||||||
self.new_alarm_weekdays = [] # Reset
|
|
||||||
self.new_alarm_weekdays.sort()
|
|
||||||
elif key in [ord('j'), 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 == 3:
|
|
||||||
# Cycle through weekday groups
|
|
||||||
if key == ord('j'):
|
|
||||||
if not self.new_alarm_weekdays:
|
|
||||||
self.new_alarm_weekdays = [5, 6] # Weekends
|
|
||||||
elif self.new_alarm_weekdays == [5, 6]:
|
|
||||||
self.new_alarm_weekdays = list(range(7)) # All days
|
|
||||||
elif self.new_alarm_weekdays == list(range(7)):
|
|
||||||
self.new_alarm_weekdays = list(range(5)) # M-F
|
|
||||||
else:
|
|
||||||
self.new_alarm_weekdays = [] # Reset
|
|
||||||
self.new_alarm_weekdays.sort()
|
|
||||||
elif key == 32: # Space
|
|
||||||
if self.new_alarm_selected == 4: # Name editing
|
|
||||||
if not self.editing_name:
|
|
||||||
self.editing_name = True
|
|
||||||
self.temp_alarm_name = self.new_alarm_name
|
|
||||||
self.new_alarm_name = ""
|
|
||||||
elif self.new_alarm_selected == 2: # Date
|
|
||||||
self.new_alarm_date = None
|
|
||||||
elif self.new_alarm_selected == 3: # Weekdays
|
|
||||||
# Toggle specific day when on weekday selection
|
|
||||||
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
|
|
||||||
|
|
||||||
# Name editing handling
|
# Up/Down for editing values
|
||||||
if self.editing_name:
|
if key in [ord('k'), curses.KEY_UP, ord('j'), curses.KEY_DOWN]:
|
||||||
|
is_up = key in [ord('k'), curses.KEY_UP]
|
||||||
|
|
||||||
|
if self.selected_item == 0: # Hour
|
||||||
|
alarm['hour'] = (alarm['hour'] + (1 if is_up else -1)) % 24
|
||||||
|
elif self.selected_item == 1: # Minute
|
||||||
|
alarm['minute'] = (alarm['minute'] + (1 if is_up else -1)) % 60
|
||||||
|
|
||||||
|
# Space key for toggling/editing
|
||||||
|
if key == 32: # SPACE
|
||||||
|
if self.selected_item == 4: # Name
|
||||||
|
if not alarm['editing_name']:
|
||||||
|
alarm['editing_name'] = True
|
||||||
|
alarm['temp_name'] = alarm['name']
|
||||||
|
alarm['name'] = ''
|
||||||
|
elif self.selected_item == 5: # Enabled
|
||||||
|
alarm['enabled'] = not alarm['enabled']
|
||||||
|
|
||||||
|
# Name editing
|
||||||
|
if alarm['editing_name']:
|
||||||
if key == curses.KEY_BACKSPACE or key == 127:
|
if key == curses.KEY_BACKSPACE or key == 127:
|
||||||
self.new_alarm_name = self.new_alarm_name[:-1]
|
alarm['name'] = alarm['name'][:-1]
|
||||||
elif 32 <= key <= 126: # Printable ASCII
|
elif 32 <= key <= 126: # Printable ASCII
|
||||||
self.new_alarm_name += chr(key)
|
alarm['name'] += chr(key)
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error: {e}")
|
|
||||||
self._show_error(str(e))
|
|
||||||
|
|
||||||
def _handle_list_alarms_input(self, key):
|
def _handle_list_alarms_input(self, key):
|
||||||
"""
|
"""Handle input for alarm list view"""
|
||||||
Handle input for the list alarms screen
|
if key == 27: # ESC
|
||||||
"""
|
self.current_view = 'CLOCK'
|
||||||
if key == 27: # Escape
|
|
||||||
self.selected_menu = 0
|
|
||||||
elif key == ord('d'):
|
elif key == ord('d'):
|
||||||
# Delete last alarm if exists
|
# Delete last alarm
|
||||||
if self.alarms:
|
if self.alarm_list:
|
||||||
last_alarm = self.alarms[-1]
|
last_alarm = self.alarm_list[-1]
|
||||||
try:
|
try:
|
||||||
self.storage.remove_saved_alert(last_alarm['id'])
|
self.storage.remove_saved_alert(last_alarm['id'])
|
||||||
|
self.alarm_list = self.storage.get_saved_alerts()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_show_error(f"Failed to delete alarm: {e}")
|
self._show_error(f"Failed to delete alarm: {e}")
|
||||||
logger.error(f"Failed to delete alarm: {e}")
|
|
||||||
|
|
||||||
def _show_error(self, message, duration=3):
|
def _show_error(self, message, duration=30):
|
||||||
"""Display an error message for a specified duration"""
|
"""Display an error message"""
|
||||||
self.error_message = message
|
self.error_message = message
|
||||||
self.error_timestamp = time.time()
|
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):
|
def _main_loop(self, stdscr):
|
||||||
"""
|
"""Main ncurses event loop"""
|
||||||
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)
|
curses.curs_set(0)
|
||||||
|
|
||||||
# Configure screen
|
|
||||||
stdscr.keypad(1)
|
stdscr.keypad(1)
|
||||||
stdscr.timeout(100)
|
stdscr.timeout(100)
|
||||||
|
|
||||||
@ -227,64 +178,59 @@ class UI:
|
|||||||
stdscr.clear()
|
stdscr.clear()
|
||||||
|
|
||||||
while not self.stop_event.is_set():
|
while not self.stop_event.is_set():
|
||||||
# Clear the screen
|
|
||||||
#stdscr.clear()
|
|
||||||
stdscr.erase()
|
stdscr.erase()
|
||||||
|
|
||||||
# Draw appropriate screen based on selected menu
|
# Draw view based on current state
|
||||||
if self.selected_menu == 0:
|
if self.current_view == 'CLOCK':
|
||||||
_draw_main_clock(stdscr)
|
_draw_main_clock(stdscr)
|
||||||
elif self.selected_menu == 1:
|
elif self.current_view == 'ADD_ALARM':
|
||||||
_draw_add_alarm(stdscr, {
|
_draw_add_alarm(stdscr, {
|
||||||
'new_alarm_selected': self.new_alarm_selected,
|
'new_alarm_selected': self.selected_item,
|
||||||
'new_alarm_name': self.new_alarm_name,
|
'new_alarm_name': self.alarm_draft['name'],
|
||||||
'editing_name': getattr(self, 'editing_name', False),
|
'new_alarm_hour': self.alarm_draft['hour'],
|
||||||
'new_alarm_hour': self.new_alarm_hour,
|
'new_alarm_minute': self.alarm_draft['minute'],
|
||||||
'new_alarm_minute': self.new_alarm_minute,
|
'new_alarm_enabled': self.alarm_draft['enabled'],
|
||||||
'new_alarm_enabled': self.new_alarm_enabled,
|
'new_alarm_date': self.alarm_draft['date'],
|
||||||
'new_alarm_date': self.new_alarm_date,
|
'new_alarm_weekdays': self.alarm_draft['weekdays'],
|
||||||
'new_alarm_weekdays': self.new_alarm_weekdays,
|
'weekday_names': self.weekday_names
|
||||||
'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:
|
elif self.current_view == 'LIST_ALARMS':
|
||||||
_draw_list_alarms(stdscr, {
|
_draw_list_alarms(stdscr, {
|
||||||
'alarms': self.alarms,
|
'alarms': self.alarm_list or [],
|
||||||
'weekday_names': self.weekday_names
|
'weekday_names': self.weekday_names
|
||||||
})
|
})
|
||||||
|
|
||||||
# Draw error if exists
|
# Render error if exists
|
||||||
if self.error_message:
|
if self.error_message:
|
||||||
_draw_error(stdscr, self.error_message)
|
_draw_error(stdscr, self.error_message)
|
||||||
self._clear_error_if_expired()
|
self._clear_error_if_expired()
|
||||||
|
|
||||||
# Refresh the screen
|
|
||||||
stdscr.refresh()
|
stdscr.refresh()
|
||||||
|
|
||||||
# Small sleep to reduce CPU usage
|
|
||||||
time.sleep(0.2)
|
|
||||||
|
|
||||||
# Handle input
|
# Handle input
|
||||||
key = stdscr.getch()
|
key = stdscr.getch()
|
||||||
if key != -1:
|
if key != -1:
|
||||||
# Menu navigation and input handling
|
if key == ord('q'):
|
||||||
if key == ord('q') or key == 27: # 'q' or Escape
|
# Context-sensitive 'q' key handling
|
||||||
if self.selected_menu != 0:
|
if self.current_view == 'CLOCK':
|
||||||
self.selected_menu = 0
|
break # Exit the application only from clock view
|
||||||
else:
|
else:
|
||||||
break
|
self.current_view = 'CLOCK' # Return to clock view from other views
|
||||||
elif key == ord('c'): # Clock/Home screen
|
continue
|
||||||
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
|
# Context-specific input handling
|
||||||
if self.selected_menu == 1:
|
if self.current_view == 'CLOCK':
|
||||||
|
self._handle_clock_input(key)
|
||||||
|
elif self.current_view == 'ADD_ALARM':
|
||||||
self._handle_add_alarm_input(key)
|
self._handle_add_alarm_input(key)
|
||||||
elif self.selected_menu == 2:
|
elif self.current_view == 'LIST_ALARMS':
|
||||||
self._handle_list_alarms_input(key)
|
self._handle_list_alarms_input(key)
|
||||||
|
|
||||||
|
time.sleep(0.2)
|
||||||
|
|
||||||
|
def _clear_error_if_expired(self):
|
||||||
|
"""Clear error message if expired"""
|
||||||
|
if self.error_message and self.error_timestamp:
|
||||||
|
if time.time() - self.error_timestamp > 3:
|
||||||
|
self.error_message = None
|
||||||
|
self.error_timestamp = None
|
||||||
|
@ -1,105 +1,109 @@
|
|||||||
from datetime import datetime
|
|
||||||
import curses
|
import curses
|
||||||
|
from datetime import datetime
|
||||||
from big_digits import BIG_DIGITS
|
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):
|
def _draw_error(stdscr, error_message):
|
||||||
"""Draw error message if present"""
|
"""Draw error message following specification"""
|
||||||
if error_message:
|
|
||||||
height, width = stdscr.getmaxyx()
|
height, width = stdscr.getmaxyx()
|
||||||
|
|
||||||
# Truncate message if too long
|
# Truncate message if too long
|
||||||
error_message = error_message[:width-4]
|
error_message = error_message[:width-4]
|
||||||
|
|
||||||
error_x = max(0, width // 2 - len(error_message) // 2)
|
error_x = max(0, width // 2 - len(error_message) // 2)
|
||||||
error_y = height - 4 # Show near bottom of screen
|
error_y = height - 4 # Show near bottom of screen
|
||||||
|
|
||||||
# Red color for errors
|
|
||||||
stdscr.attron(curses.color_pair(3) | curses.A_BOLD)
|
stdscr.attron(curses.color_pair(3) | curses.A_BOLD)
|
||||||
stdscr.addstr(error_y, error_x, error_message)
|
stdscr.addstr(error_y, error_x, error_message)
|
||||||
stdscr.attroff(curses.color_pair(3) | curses.A_BOLD)
|
stdscr.attroff(curses.color_pair(3) | curses.A_BOLD)
|
||||||
|
|
||||||
def _draw_big_digit(stdscr, y, x, digit, big_digits):
|
def _draw_big_digit(stdscr, y, x, digit):
|
||||||
"""
|
"""Draw a big digit using predefined patterns"""
|
||||||
Draw a big digit using predefined patterns
|
patterns = BIG_DIGITS[digit]
|
||||||
"""
|
|
||||||
patterns = big_digits[digit]
|
|
||||||
for i, line in enumerate(patterns):
|
for i, line in enumerate(patterns):
|
||||||
stdscr.addstr(y + i, x, line)
|
stdscr.addstr(y + i, x, line)
|
||||||
|
|
||||||
def _draw_big_time(stdscr, big_digits):
|
def _draw_main_clock(stdscr, context=None):
|
||||||
"""
|
"""Draw the main clock screen"""
|
||||||
Draw the time in big digits
|
_init_colors()
|
||||||
"""
|
|
||||||
current_time = datetime.now()
|
|
||||||
time_str = current_time.strftime("%H:%M:%S")
|
|
||||||
|
|
||||||
# Get terminal dimensions
|
|
||||||
height, width = stdscr.getmaxyx()
|
height, width = stdscr.getmaxyx()
|
||||||
|
current_time = datetime.now()
|
||||||
|
|
||||||
# Calculate starting position to center the big clock
|
# Big time display
|
||||||
digit_width = 14 # Width of each digit pattern including spacing
|
time_str = current_time.strftime("%H:%M:%S")
|
||||||
|
digit_width = 14 # Width of each digit pattern
|
||||||
total_width = digit_width * len(time_str)
|
total_width = digit_width * len(time_str)
|
||||||
start_x = (width - total_width) // 2
|
start_x = (width - total_width) // 2
|
||||||
start_y = (height - 7) // 2 - 4 # Move up a bit to make room for date
|
start_y = (height - 7) // 2 - 4
|
||||||
|
|
||||||
# Color for the big time
|
# Green color for big time
|
||||||
stdscr.attron(curses.color_pair(1))
|
stdscr.attron(curses.color_pair(1))
|
||||||
for i, digit in enumerate(time_str):
|
for i, digit in enumerate(time_str):
|
||||||
_draw_big_digit(stdscr, start_y, start_x + i * digit_width, digit, big_digits)
|
_draw_big_digit(stdscr, start_y, start_x + i * digit_width, digit)
|
||||||
stdscr.attroff(curses.color_pair(1))
|
stdscr.attroff(curses.color_pair(1))
|
||||||
|
|
||||||
def _draw_main_clock(stdscr):
|
# Date display
|
||||||
"""
|
|
||||||
Draw the main clock screen
|
|
||||||
"""
|
|
||||||
current_time = datetime.now()
|
|
||||||
time_str = current_time.strftime("%H:%M:%S")
|
|
||||||
date_str = current_time.strftime("%Y-%m-%d")
|
date_str = current_time.strftime("%Y-%m-%d")
|
||||||
|
|
||||||
# Get terminal dimensions
|
|
||||||
height, width = stdscr.getmaxyx()
|
|
||||||
|
|
||||||
# Draw big time
|
|
||||||
# Note: You'll need to pass BIG_DIGITS from big_digits module when calling
|
|
||||||
_draw_big_time(stdscr, BIG_DIGITS)
|
|
||||||
|
|
||||||
# Draw date
|
|
||||||
date_x = width // 2 - len(date_str) // 2
|
date_x = width // 2 - len(date_str) // 2
|
||||||
date_y = height // 2 + 4 # Below the big clock
|
date_y = height // 2 + 4
|
||||||
|
|
||||||
stdscr.attron(curses.color_pair(2))
|
stdscr.attron(curses.color_pair(2))
|
||||||
stdscr.addstr(date_y, date_x, date_str)
|
stdscr.addstr(date_y, date_x, date_str)
|
||||||
stdscr.attroff(curses.color_pair(2))
|
stdscr.attroff(curses.color_pair(2))
|
||||||
|
|
||||||
# Draw menu options
|
# Menu options
|
||||||
menu_str = "A: Add Alarm L: List Alarms Q: Quit"
|
menu_str = "A: Add Alarm S: List Alarms Q: Quit"
|
||||||
menu_x = width // 2 - len(menu_str) // 2
|
menu_x = width // 2 - len(menu_str) // 2
|
||||||
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
|
||||||
|
if context is None:
|
||||||
|
context = {}
|
||||||
|
|
||||||
Args:
|
# Provide default values with more explicit checks
|
||||||
stdscr: The curses screen object
|
context = {
|
||||||
context: A dictionary containing UI state variables
|
'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()
|
height, width = stdscr.getmaxyx()
|
||||||
|
|
||||||
form_y = height // 2 - 3
|
form_y = height // 2 - 3
|
||||||
stdscr.addstr(form_y -1, width // 2 - 10, "Add New Alarm")
|
|
||||||
|
# Title
|
||||||
|
stdscr.addstr(form_y - 1, width // 2 - 10, "Add New Alarm")
|
||||||
|
|
||||||
# Name input
|
# Name input
|
||||||
|
name_str = str(context['new_alarm_name'])
|
||||||
if context['new_alarm_selected'] == 4:
|
if context['new_alarm_selected'] == 4:
|
||||||
stdscr.attron(curses.color_pair(2))
|
stdscr.attron(curses.color_pair(2))
|
||||||
stdscr.addstr(form_y + 1, width // 2 - 10, f"Name: {context['new_alarm_name']}")
|
stdscr.addstr(form_y + 1, width // 2 - 10, f"Name: {name_str}")
|
||||||
if context['new_alarm_selected'] == 4:
|
if context['new_alarm_selected'] == 4:
|
||||||
stdscr.attroff(curses.color_pair(2))
|
stdscr.attroff(curses.color_pair(2))
|
||||||
|
|
||||||
# Time selection
|
# Time selection
|
||||||
hour_str = f"{context['new_alarm_hour']:02d}"
|
hour_str = f"{int(context['new_alarm_hour']):02d}"
|
||||||
minute_str = f"{context['new_alarm_minute']:02d}"
|
minute_str = f"{int(context['new_alarm_minute']):02d}"
|
||||||
|
|
||||||
# Highlight selected field
|
# Highlight time components
|
||||||
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)
|
stdscr.addstr(form_y + 2, width // 2 - 2, hour_str)
|
||||||
@ -114,7 +118,7 @@ def _draw_add_alarm(stdscr, context):
|
|||||||
if context['new_alarm_selected'] == 1:
|
if context['new_alarm_selected'] == 1:
|
||||||
stdscr.attroff(curses.color_pair(2))
|
stdscr.attroff(curses.color_pair(2))
|
||||||
|
|
||||||
# Enabled/Disabled toggle
|
# Enabled toggle
|
||||||
enabled_str = "Enabled" if context['new_alarm_enabled'] else "Disabled"
|
enabled_str = "Enabled" if context['new_alarm_enabled'] else "Disabled"
|
||||||
if context['new_alarm_selected'] == 5:
|
if context['new_alarm_selected'] == 5:
|
||||||
stdscr.attron(curses.color_pair(2))
|
stdscr.attron(curses.color_pair(2))
|
||||||
@ -123,7 +127,11 @@ def _draw_add_alarm(stdscr, context):
|
|||||||
stdscr.attroff(curses.color_pair(2))
|
stdscr.attroff(curses.color_pair(2))
|
||||||
|
|
||||||
# Date selection
|
# Date selection
|
||||||
date_str = "No specific date" if not context['new_alarm_date'] else context['new_alarm_date'].strftime("%Y-%m-%d")
|
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:
|
if context['new_alarm_selected'] == 2:
|
||||||
stdscr.attron(curses.color_pair(2))
|
stdscr.attron(curses.color_pair(2))
|
||||||
stdscr.addstr(form_y + 3, width // 2 - len(date_str) // 2, date_str)
|
stdscr.addstr(form_y + 3, width // 2 - len(date_str) // 2, date_str)
|
||||||
@ -131,10 +139,12 @@ def _draw_add_alarm(stdscr, context):
|
|||||||
stdscr.attroff(curses.color_pair(2))
|
stdscr.attroff(curses.color_pair(2))
|
||||||
|
|
||||||
# Weekday selection
|
# Weekday selection
|
||||||
|
weekday_names = context['weekday_names']
|
||||||
weekday_str = "Repeat: " + " ".join(
|
weekday_str = "Repeat: " + " ".join(
|
||||||
context['weekday_names'][i] if i in context['new_alarm_weekdays'] else "___"
|
weekday_names[i] if i in context['new_alarm_weekdays'] else "___"
|
||||||
for i in range(7)
|
for i in range(len(weekday_names))
|
||||||
)
|
)
|
||||||
|
|
||||||
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)
|
stdscr.addstr(form_y + 5, width // 2 - len(weekday_str) // 2, weekday_str)
|
||||||
@ -143,26 +153,23 @@ def _draw_add_alarm(stdscr, context):
|
|||||||
|
|
||||||
# Instructions
|
# Instructions
|
||||||
stdscr.addstr(height - 2, 2,
|
stdscr.addstr(height - 2, 2,
|
||||||
"↑↓: Change ←→: Switch Space: Toggle Enter: Save Esc: Cancel")
|
"jk: Change hl: Switch Space: Toggle Enter: Save Esc: Cancel")
|
||||||
|
|
||||||
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()
|
||||||
|
|
||||||
Args:
|
|
||||||
stdscr: The curses screen object
|
|
||||||
context: A dictionary containing UI state variables
|
|
||||||
"""
|
|
||||||
height, width = stdscr.getmaxyx()
|
height, width = stdscr.getmaxyx()
|
||||||
|
|
||||||
# Header
|
# Header
|
||||||
stdscr.addstr(2, width // 2 - 5, "Alarms")
|
stdscr.addstr(2, width // 2 - 5, "Alarms")
|
||||||
|
|
||||||
if not context['alarms']:
|
if not context.get('alarms'):
|
||||||
stdscr.addstr(4, width // 2 - 10, "No alarms set")
|
stdscr.addstr(4, width // 2 - 10, "No alarms set")
|
||||||
else:
|
else:
|
||||||
|
weekday_names = context.get('weekday_names', ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'])
|
||||||
|
|
||||||
for i, alarm in enumerate(context['alarms'][:height-6]):
|
for i, alarm in enumerate(context['alarms'][:height-6]):
|
||||||
# Format time and repeat information
|
# Format time
|
||||||
time_str = alarm.get('time', 'Unknown')
|
time_str = alarm.get('time', 'Unknown')
|
||||||
|
|
||||||
# Format repeat info
|
# Format repeat info
|
||||||
@ -171,17 +178,18 @@ def _draw_list_alarms(stdscr, context):
|
|||||||
if repeat_rule:
|
if repeat_rule:
|
||||||
if repeat_rule.get('type') == 'weekly':
|
if repeat_rule.get('type') == 'weekly':
|
||||||
days = repeat_rule.get('days', [])
|
days = repeat_rule.get('days', [])
|
||||||
repeat_info = f" (Every {', '.join(context['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
|
||||||
status = "✓" if alarm.get('enabled', True) else "✗"
|
status = "✓" if alarm.get('enabled', True) else "✗"
|
||||||
display_str = f"{status} {time_str}{repeat_info}"
|
display_str = f"{status} {time_str} {alarm.get('name', 'Unnamed')}{repeat_info}"
|
||||||
|
|
||||||
# Truncate if too long
|
# Truncate if too long
|
||||||
display_str = display_str[:width-4]
|
display_str = display_str[:width-4]
|
||||||
|
|
||||||
stdscr.addstr(4 + i, 2, display_str)
|
stdscr.addstr(4 + i, 2, display_str)
|
||||||
|
|
||||||
|
# Instructions
|
||||||
stdscr.addstr(height - 2, 2, "D: Delete Enter: Edit Esc: Back")
|
stdscr.addstr(height - 2, 2, "D: Delete Enter: Edit Esc: Back")
|
||||||
|
@ -74,100 +74,100 @@ fi
|
|||||||
|
|
||||||
echo -e "${GREEN}Added alarm with ID: $NEW_ALARM_ID${NC}"
|
echo -e "${GREEN}Added alarm with ID: $NEW_ALARM_ID${NC}"
|
||||||
|
|
||||||
# 4. Test duplicate alarm detection
|
#j# 4. Test duplicate alarm detection
|
||||||
print_status "Testing duplicate alarm detection"
|
#jprint_status "Testing duplicate alarm detection"
|
||||||
DUPLICATE_RESPONSE=$(curl -s -X POST -H "Content-Type: application/json" -d "$POST_DATA" "$API_URL")
|
#jDUPLICATE_RESPONSE=$(curl -s -X POST -H "Content-Type: application/json" -d "$POST_DATA" "$API_URL")
|
||||||
if [[ $(echo "$DUPLICATE_RESPONSE" | jq -r '.error') == *"Duplicate alarm detected"* ]]; then
|
#jif [[ $(echo "$DUPLICATE_RESPONSE" | jq -r '.error') == *"Duplicate alarm detected"* ]]; then
|
||||||
echo -e "${GREEN}✓ Duplicate detection working${NC}"
|
#j echo -e "${GREEN}✓ Duplicate detection working${NC}"
|
||||||
else
|
#jelse
|
||||||
echo -e "${RED}✗ Duplicate detection failed${NC}"
|
#j echo -e "${RED}✗ Duplicate detection failed${NC}"
|
||||||
echo "Response: $DUPLICATE_RESPONSE"
|
#j echo "Response: $DUPLICATE_RESPONSE"
|
||||||
fi
|
#jfi
|
||||||
|
#j
|
||||||
# 5. Update the alarm (PUT)
|
#j# 5. Update the alarm (PUT)
|
||||||
print_status "Updating alarm"
|
#jprint_status "Updating alarm"
|
||||||
UPDATE_DATA=$(jq -n \
|
#jUPDATE_DATA=$(jq -n \
|
||||||
--arg name "$TEST_ALARM_NAME Updated" \
|
#j --arg name "$TEST_ALARM_NAME Updated" \
|
||||||
--arg time "$TEST_ALARM_TIME" \
|
#j --arg time "$TEST_ALARM_TIME" \
|
||||||
--argjson id "$NEW_ALARM_ID" \
|
#j --argjson id "$NEW_ALARM_ID" \
|
||||||
'{
|
#j '{
|
||||||
"id": $id,
|
#j "id": $id,
|
||||||
"name": $name,
|
#j "name": $name,
|
||||||
"time": $time,
|
#j "time": $time,
|
||||||
"repeat_rule": {
|
#j "repeat_rule": {
|
||||||
"type": "daily"
|
#j "type": "daily"
|
||||||
},
|
#j },
|
||||||
"enabled": true,
|
#j "enabled": true,
|
||||||
"snooze": {
|
#j "snooze": {
|
||||||
"enabled": false,
|
#j "enabled": false,
|
||||||
"duration": 10,
|
#j "duration": 10,
|
||||||
"max_count": 2
|
#j "max_count": 2
|
||||||
},
|
#j },
|
||||||
"metadata": {
|
#j "metadata": {
|
||||||
"volume": 90,
|
#j "volume": 90,
|
||||||
"notes": "Updated test alarm"
|
#j "notes": "Updated test alarm"
|
||||||
}
|
#j }
|
||||||
}'
|
#j }'
|
||||||
)
|
#j)
|
||||||
|
#j
|
||||||
UPDATE_RESPONSE=$(curl -s -X PUT -H "Content-Type: application/json" -d "$UPDATE_DATA" "$API_URL")
|
#jUPDATE_RESPONSE=$(curl -s -X PUT -H "Content-Type: application/json" -d "$UPDATE_DATA" "$API_URL")
|
||||||
if [[ $(echo "$UPDATE_RESPONSE" | jq -r '.data.message') == "Alarm updated successfully" ]]; then
|
#jif [[ $(echo "$UPDATE_RESPONSE" | jq -r '.data.message') == "Alarm updated successfully" ]]; then
|
||||||
echo -e "${GREEN}✓ Alarm update successful${NC}"
|
#j echo -e "${GREEN}✓ Alarm update successful${NC}"
|
||||||
else
|
#jelse
|
||||||
echo -e "${RED}✗ Alarm update failed${NC}"
|
#j echo -e "${RED}✗ Alarm update failed${NC}"
|
||||||
echo "Response: $UPDATE_RESPONSE"
|
#j echo "Response: $UPDATE_RESPONSE"
|
||||||
fi
|
#jfi
|
||||||
|
#j
|
||||||
# 6. Verify the update
|
#j# 6. Verify the update
|
||||||
print_status "Verifying update"
|
#jprint_status "Verifying update"
|
||||||
UPDATED_STATE=$(curl -s -X GET "$API_URL")
|
#jUPDATED_STATE=$(curl -s -X GET "$API_URL")
|
||||||
UPDATED_ALARM=$(echo "$UPDATED_STATE" | jq -r ".data[] | select(.id == $NEW_ALARM_ID)")
|
#jUPDATED_ALARM=$(echo "$UPDATED_STATE" | jq -r ".data[] | select(.id == $NEW_ALARM_ID)")
|
||||||
if [[ $(echo "$UPDATED_ALARM" | jq -r '.name') == "$TEST_ALARM_NAME Updated" ]]; then
|
#jif [[ $(echo "$UPDATED_ALARM" | jq -r '.name') == "$TEST_ALARM_NAME Updated" ]]; then
|
||||||
echo -e "${GREEN}✓ Update verification successful${NC}"
|
#j echo -e "${GREEN}✓ Update verification successful${NC}"
|
||||||
else
|
#jelse
|
||||||
echo -e "${RED}✗ Update verification failed${NC}"
|
#j echo -e "${RED}✗ Update verification failed${NC}"
|
||||||
echo "Current alarm state: $UPDATED_ALARM"
|
#j echo "Current alarm state: $UPDATED_ALARM"
|
||||||
fi
|
#jfi
|
||||||
|
#j
|
||||||
# 7. Test invalid inputs
|
#j# 7. Test invalid inputs
|
||||||
print_status "Testing invalid inputs"
|
#jprint_status "Testing invalid inputs"
|
||||||
|
#j
|
||||||
# Test invalid time format
|
#j# Test invalid time format
|
||||||
INVALID_TIME_DATA=$(echo "$POST_DATA" | jq '. + {"time": "25:00:00"}')
|
#jINVALID_TIME_DATA=$(echo "$POST_DATA" | jq '. + {"time": "25:00:00"}')
|
||||||
INVALID_TIME_RESPONSE=$(curl -s -X POST -H "Content-Type: application/json" -d "$INVALID_TIME_DATA" "$API_URL")
|
#jINVALID_TIME_RESPONSE=$(curl -s -X POST -H "Content-Type: application/json" -d "$INVALID_TIME_DATA" "$API_URL")
|
||||||
if [[ $(echo "$INVALID_TIME_RESPONSE" | jq -r '.error') == *"Invalid alarm configuration"* ]]; then
|
#jif [[ $(echo "$INVALID_TIME_RESPONSE" | jq -r '.error') == *"Invalid alarm configuration"* ]]; then
|
||||||
echo -e "${GREEN}✓ Invalid time format detection working${NC}"
|
#j echo -e "${GREEN}✓ Invalid time format detection working${NC}"
|
||||||
else
|
#jelse
|
||||||
echo -e "${RED}✗ Invalid time format detection failed${NC}"
|
#j echo -e "${RED}✗ Invalid time format detection failed${NC}"
|
||||||
fi
|
#jfi
|
||||||
|
#j
|
||||||
# Test invalid repeat rule
|
#j# Test invalid repeat rule
|
||||||
INVALID_REPEAT_DATA=$(echo "$POST_DATA" | jq '.repeat_rule.type = "monthly"')
|
#jINVALID_REPEAT_DATA=$(echo "$POST_DATA" | jq '.repeat_rule.type = "monthly"')
|
||||||
INVALID_REPEAT_RESPONSE=$(curl -s -X POST -H "Content-Type: application/json" -d "$INVALID_REPEAT_DATA" "$API_URL")
|
#jINVALID_REPEAT_RESPONSE=$(curl -s -X POST -H "Content-Type: application/json" -d "$INVALID_REPEAT_DATA" "$API_URL")
|
||||||
if [[ $(echo "$INVALID_REPEAT_RESPONSE" | jq -r '.error') == *"Invalid alarm configuration"* ]]; then
|
#jif [[ $(echo "$INVALID_REPEAT_RESPONSE" | jq -r '.error') == *"Invalid alarm configuration"* ]]; then
|
||||||
echo -e "${GREEN}✓ Invalid repeat rule detection working${NC}"
|
#j echo -e "${GREEN}✓ Invalid repeat rule detection working${NC}"
|
||||||
else
|
#jelse
|
||||||
echo -e "${RED}✗ Invalid repeat rule detection failed${NC}"
|
#j echo -e "${RED}✗ Invalid repeat rule detection failed${NC}"
|
||||||
fi
|
#jfi
|
||||||
|
#j
|
||||||
# 8. Delete the test alarm
|
#j# 8. Delete the test alarm
|
||||||
print_status "Deleting test alarm"
|
#jprint_status "Deleting test alarm"
|
||||||
DELETE_RESPONSE=$(curl -s -X DELETE -H "Content-Type: application/json" -d "{\"id\":$NEW_ALARM_ID}" "$API_URL")
|
#jDELETE_RESPONSE=$(curl -s -X DELETE -H "Content-Type: application/json" -d "{\"id\":$NEW_ALARM_ID}" "$API_URL")
|
||||||
if [[ $(echo "$DELETE_RESPONSE" | jq -r '.data.message') == "Alarm removed successfully" ]]; then
|
#jif [[ $(echo "$DELETE_RESPONSE" | jq -r '.data.message') == "Alarm removed successfully" ]]; then
|
||||||
echo -e "${GREEN}✓ Alarm deletion successful${NC}"
|
#j echo -e "${GREEN}✓ Alarm deletion successful${NC}"
|
||||||
else
|
#jelse
|
||||||
echo -e "${RED}✗ Alarm deletion failed${NC}"
|
#j echo -e "${RED}✗ Alarm deletion failed${NC}"
|
||||||
echo "Response: $DELETE_RESPONSE"
|
#j echo "Response: $DELETE_RESPONSE"
|
||||||
fi
|
#jfi
|
||||||
|
#j
|
||||||
# 9. Verify deletion
|
#j# 9. Verify deletion
|
||||||
print_status "Verifying deletion"
|
#jprint_status "Verifying deletion"
|
||||||
FINAL_STATE=$(curl -s -X GET "$API_URL")
|
#jFINAL_STATE=$(curl -s -X GET "$API_URL")
|
||||||
if [[ $(echo "$FINAL_STATE" | jq ".data[] | select(.id == $NEW_ALARM_ID)") == "" ]]; then
|
#jif [[ $(echo "$FINAL_STATE" | jq ".data[] | select(.id == $NEW_ALARM_ID)") == "" ]]; then
|
||||||
echo -e "${GREEN}✓ Deletion verification successful${NC}"
|
#j echo -e "${GREEN}✓ Deletion verification successful${NC}"
|
||||||
else
|
#jelse
|
||||||
echo -e "${RED}✗ Deletion verification failed${NC}"
|
#j echo -e "${RED}✗ Deletion verification failed${NC}"
|
||||||
echo "Current state: $FINAL_STATE"
|
#j echo "Current state: $FINAL_STATE"
|
||||||
fi
|
#jfi
|
||||||
|
#j
|
||||||
print_status "Test suite completed!"
|
#jprint_status "Test suite completed!"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user