python import problems.
This commit is contained in:
parent
7e5d1a38a6
commit
211f581921
0
alert_clients/__init__.py
Normal file
0
alert_clients/__init__.py
Normal file
BIN
alert_clients/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
alert_clients/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
0
alert_clients/alarm_api_client/__init__.py
Normal file
0
alert_clients/alarm_api_client/__init__.py
Normal file
@ -4,8 +4,7 @@ import argparse
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from alarm_api_client import AlarmApiClient
|
from alert_clients.alarm_api_client.alarm_api_client import AlarmApiClient
|
||||||
|
|
||||||
|
|
||||||
CONFIG_FILE = Path.home() / ".alarms.json"
|
CONFIG_FILE = Path.home() / ".alarms.json"
|
||||||
|
|
0
alert_clients/ncurses_client/__init__.py
Normal file
0
alert_clients/ncurses_client/__init__.py
Normal file
Binary file not shown.
BIN
alert_clients/ncurses_client/__pycache__/main.cpython-312.pyc
Normal file
BIN
alert_clients/ncurses_client/__pycache__/main.cpython-312.pyc
Normal file
Binary file not shown.
Binary file not shown.
78
alert_clients/ncurses_client/alarm-logger.py
Normal file
78
alert_clients/ncurses_client/alarm-logger.py
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from logging.handlers import RotatingFileHandler
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
class AlarmLogger:
|
||||||
|
def __init__(self, log_dir='logs', log_level=logging.INFO):
|
||||||
|
"""
|
||||||
|
Initialize a comprehensive logging system for the alarm application.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
log_dir (str): Directory to store log files
|
||||||
|
log_level (int): Logging level (default: logging.INFO)
|
||||||
|
"""
|
||||||
|
# Ensure log directory exists
|
||||||
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Create a unique log filename with timestamp
|
||||||
|
log_filename = os.path.join(
|
||||||
|
log_dir,
|
||||||
|
f"alarm_app_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Configure the root logger
|
||||||
|
logging.basicConfig(
|
||||||
|
level=log_level,
|
||||||
|
format='%(asctime)s | %(levelname)8s | %(module)15s:%(lineno)4d | %(message)s',
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create rotating file handler
|
||||||
|
file_handler = RotatingFileHandler(
|
||||||
|
log_filename,
|
||||||
|
maxBytes=10*1024*1024, # 10 MB
|
||||||
|
backupCount=5
|
||||||
|
)
|
||||||
|
file_handler.setFormatter(logging.Formatter(
|
||||||
|
'%(asctime)s | %(levelname)8s | %(module)15s:%(lineno)4d | %(message)s',
|
||||||
|
datefmt='%Y-%m-%d %H:%M:%S'
|
||||||
|
))
|
||||||
|
|
||||||
|
# Create console handler
|
||||||
|
console_handler = logging.StreamHandler()
|
||||||
|
console_handler.setFormatter(logging.Formatter(
|
||||||
|
'%(levelname)8s | %(module)15s:%(lineno)4d | %(message)s'
|
||||||
|
))
|
||||||
|
|
||||||
|
# Get the root logger and add handlers
|
||||||
|
root_logger = logging.getLogger()
|
||||||
|
root_logger.addHandler(file_handler)
|
||||||
|
root_logger.addHandler(console_handler)
|
||||||
|
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def log_alarm_created(self, alarm_details):
|
||||||
|
"""Log alarm creation details."""
|
||||||
|
self.logger.info(f"Alarm Created: {alarm_details}")
|
||||||
|
|
||||||
|
def log_alarm_triggered(self, alarm_id):
|
||||||
|
"""Log when an alarm is triggered."""
|
||||||
|
self.logger.warning(f"Alarm Triggered: ID {alarm_id}")
|
||||||
|
|
||||||
|
def log_alarm_snoozed(self, alarm_id, snooze_duration):
|
||||||
|
"""Log alarm snooze details."""
|
||||||
|
self.logger.info(f"Alarm Snoozed: ID {alarm_id}, Duration: {snooze_duration} minutes")
|
||||||
|
|
||||||
|
def log_system_error(self, error_message, exc_info=False):
|
||||||
|
"""Log system errors with optional exception details."""
|
||||||
|
self.logger.error(error_message, exc_info=exc_info)
|
||||||
|
|
||||||
|
def log_api_interaction(self, method, endpoint, status):
|
||||||
|
"""Log API interactions."""
|
||||||
|
self.logger.info(f"API {method.upper()}: {endpoint} - Status: {status}")
|
||||||
|
|
||||||
|
# Example usage in other modules
|
||||||
|
# logger = AlarmLogger()
|
||||||
|
# logger.log_alarm_created({"time": "07:00", "repeat": "daily"})
|
||||||
|
# logger.log_system_error("Failed to connect to API", exc_info=True)
|
88
alert_clients/ncurses_client/alert_logic.py
Normal file
88
alert_clients/ncurses_client/alert_logic.py
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
from datetime import datetime, timedelta
|
||||||
|
from alarm_logger import AlarmLogger
|
||||||
|
|
||||||
|
class AlarmLogic:
|
||||||
|
def __init__(self, api_client):
|
||||||
|
self.logger = AlarmLogger()
|
||||||
|
self.api_client = api_client
|
||||||
|
self.alarms = []
|
||||||
|
self.current_alarm = None
|
||||||
|
self.snooze_minutes = 5
|
||||||
|
self.snooze_until = None
|
||||||
|
|
||||||
|
def refresh_alarms(self):
|
||||||
|
"""Fetches alarms from the API and updates the local list."""
|
||||||
|
try:
|
||||||
|
self.alarms = self.api_client.get_alarms()
|
||||||
|
except Exception as e:
|
||||||
|
raise RuntimeError(f"Failed to fetch alarms: {str(e)}")
|
||||||
|
|
||||||
|
def create_alarm(self, hour, minute, date=None, weekdays=None):
|
||||||
|
"""Creates a new alarm."""
|
||||||
|
alarm_data = {
|
||||||
|
"name": f"Alarm {hour:02d}:{minute:02d}",
|
||||||
|
"time": f"{hour:02d}:{minute:02d}:00",
|
||||||
|
"enabled": True,
|
||||||
|
"repeat_rule": {"type": "once"},
|
||||||
|
"snooze": {
|
||||||
|
"enabled": True,
|
||||||
|
"duration": self.snooze_minutes,
|
||||||
|
"max_count": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if weekdays:
|
||||||
|
alarm_data["repeat_rule"] = {
|
||||||
|
"type": "weekly",
|
||||||
|
"days": weekdays
|
||||||
|
}
|
||||||
|
elif date:
|
||||||
|
alarm_data["repeat_rule"] = {
|
||||||
|
"type": "once",
|
||||||
|
"date": date.strftime("%Y-%m-%d")
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.api_client.create_alarm(alarm_data)
|
||||||
|
self.refresh_alarms()
|
||||||
|
self.logger.log_alarm_created(alarm_data)
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.log_system_error(f"Alarm creation failed: {e}", exc_info=True)
|
||||||
|
raise RuntimeError(f"Failed to create alarm: {str(e)}")
|
||||||
|
|
||||||
|
def stop_alarm(self):
|
||||||
|
"""Stops the current alarm."""
|
||||||
|
if self.current_alarm:
|
||||||
|
try:
|
||||||
|
alarm_data = self.current_alarm.copy()
|
||||||
|
alarm_data["enabled"] = False
|
||||||
|
self.api_client.update_alarm(self.current_alarm["id"], alarm_data)
|
||||||
|
self.current_alarm = None
|
||||||
|
self.snooze_until = None
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.log_system_error(f"Failed to stop alarm: {e}", exc_info=True)
|
||||||
|
raise RuntimeError(f"Failed to stop alarm: {str(e)}")
|
||||||
|
|
||||||
|
def snooze_alarm(self):
|
||||||
|
"""Snoozes the current alarm."""
|
||||||
|
if self.current_alarm:
|
||||||
|
self.snooze_until = datetime.now() + timedelta(minutes=self.snooze_minutes)
|
||||||
|
self.stop_alarm()
|
||||||
|
|
||||||
|
def check_alarms(self):
|
||||||
|
"""Checks if any alarm should trigger based on the current time."""
|
||||||
|
current_time = datetime.now()
|
||||||
|
|
||||||
|
if self.snooze_until and current_time >= self.snooze_until:
|
||||||
|
self.current_alarm = {"time": self.snooze_until.strftime("%H:%M:%S")}
|
||||||
|
self.snooze_until = None
|
||||||
|
|
||||||
|
if not self.current_alarm:
|
||||||
|
self.refresh_alarms()
|
||||||
|
for alarm in self.alarms:
|
||||||
|
alarm_time = datetime.strptime(alarm["time"], "%H:%M:%S").time()
|
||||||
|
if (alarm_time.hour == current_time.hour and
|
||||||
|
alarm_time.minute == current_time.minute and
|
||||||
|
alarm["enabled"]):
|
||||||
|
self.current_alarm = alarm
|
||||||
|
break
|
18
alert_clients/ncurses_client/main.py
Executable file
18
alert_clients/ncurses_client/main.py
Executable file
@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import curses
|
||||||
|
from ncurses_handlers import AlarmClock
|
||||||
|
from alarm_api_client.alarm_api_client import AlarmApiClient
|
||||||
|
|
||||||
|
def main(stdscr):
|
||||||
|
"""Main entry point for the ncurses alarm clock client."""
|
||||||
|
try:
|
||||||
|
alarm_clock = AlarmClock(stdscr)
|
||||||
|
alarm_clock.run()
|
||||||
|
except Exception as e:
|
||||||
|
# Fallback error handling
|
||||||
|
curses.endwin()
|
||||||
|
print(f"An error occurred: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
curses.wrapper(main)
|
212
alert_clients/ncurses_client/ncurses_handlers.py
Normal file
212
alert_clients/ncurses_client/ncurses_handlers.py
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
import curses
|
||||||
|
from datetime import datetime, date, timedelta
|
||||||
|
import time
|
||||||
|
from threading import Thread
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from alert_clients.alarm_api_client.alarm_api_client import AlarmApiClient
|
||||||
|
#from alarm_api_client.alarm_api_client import AlarmApiClient
|
||||||
|
from ncurses_ui import NcursesUI
|
||||||
|
from alert_logic import AlarmLogic
|
||||||
|
from ncurses_threads import NcursesThreads
|
||||||
|
from alarm_logger import AlarmLogger
|
||||||
|
|
||||||
|
class AlarmClock:
|
||||||
|
def __init__(self, stdscr):
|
||||||
|
# Initialize logging first
|
||||||
|
self.logger = AlarmLogger()
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.stdscr = stdscr
|
||||||
|
self.api_client = AlarmApiClient("http://localhost:8000")
|
||||||
|
self.alarm_logic = AlarmLogic(self.api_client)
|
||||||
|
self.ncurses_ui = NcursesUI(stdscr)
|
||||||
|
self.ncurses_threads = NcursesThreads(self.alarm_logic)
|
||||||
|
|
||||||
|
self.alarms = []
|
||||||
|
self.running = True
|
||||||
|
self.selected_menu = 0
|
||||||
|
self.new_alarm_hour = 0
|
||||||
|
self.new_alarm_minute = 0
|
||||||
|
self.new_alarm_selected = 0
|
||||||
|
self.new_alarm_date = None
|
||||||
|
self.new_alarm_weekdays = []
|
||||||
|
self.weekday_names = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
||||||
|
self.current_alarm = None
|
||||||
|
self.current_alarm_process = False
|
||||||
|
self.snooze_minutes = 5
|
||||||
|
self.snooze_until = None
|
||||||
|
|
||||||
|
# Initialize curses
|
||||||
|
self.stdscr.keypad(1)
|
||||||
|
self.stdscr.timeout(100)
|
||||||
|
|
||||||
|
self.logger.log_system_error("Alarm Clock Initialized Successfully")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.log_system_error(f"Initialization Error: {str(e)}", exc_info=True)
|
||||||
|
raise
|
||||||
|
|
||||||
|
def show_error(self, message):
|
||||||
|
self.logger.log_system_error(message)
|
||||||
|
self.ncurses_ui.show_error(message)
|
||||||
|
|
||||||
|
def handle_add_alarm_input(self, key):
|
||||||
|
try:
|
||||||
|
if key == 27: # Escape
|
||||||
|
self.selected_menu = 0
|
||||||
|
self.logger.log_system_error("Add Alarm menu cancelled")
|
||||||
|
return
|
||||||
|
|
||||||
|
if key == 10: # Enter
|
||||||
|
try:
|
||||||
|
alarm_details = {
|
||||||
|
"hour": self.new_alarm_hour,
|
||||||
|
"minute": self.new_alarm_minute,
|
||||||
|
"date": self.new_alarm_date,
|
||||||
|
"weekdays": self.new_alarm_weekdays if self.new_alarm_weekdays else None
|
||||||
|
}
|
||||||
|
self.alarm_logic.create_alarm(
|
||||||
|
self.new_alarm_hour,
|
||||||
|
self.new_alarm_minute,
|
||||||
|
self.new_alarm_date,
|
||||||
|
self.new_alarm_weekdays if self.new_alarm_weekdays else None
|
||||||
|
)
|
||||||
|
self.logger.log_alarm_created(alarm_details)
|
||||||
|
self.alarms = self.api_client.get_alarms()
|
||||||
|
self.selected_menu = 0
|
||||||
|
except Exception as e:
|
||||||
|
self.show_error(f"Failed to create alarm: {str(e)}")
|
||||||
|
return
|
||||||
|
|
||||||
|
if key == curses.KEY_LEFT:
|
||||||
|
self.new_alarm_selected = (self.new_alarm_selected - 1) % 4
|
||||||
|
self.logger.log_system_error(f"Selected field changed to {self.new_alarm_selected}")
|
||||||
|
elif key == curses.KEY_RIGHT:
|
||||||
|
self.new_alarm_selected = (self.new_alarm_selected + 1) % 4
|
||||||
|
self.logger.log_system_error(f"Selected field changed to {self.new_alarm_selected}")
|
||||||
|
elif key == 32: # Space
|
||||||
|
if self.new_alarm_selected == 2: # Date
|
||||||
|
self.new_alarm_date = None
|
||||||
|
self.logger.log_system_error(f"Selected field changed to DATE")
|
||||||
|
elif self.new_alarm_selected == 3: # Weekdays
|
||||||
|
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 key == 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 key == 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)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.log_system_error(f"Error in add alarm input: {str(e)}", exc_info=True)
|
||||||
|
|
||||||
|
def delete_selected_alarm(self):
|
||||||
|
try:
|
||||||
|
if self.alarms:
|
||||||
|
alarm = self.alarms[-1] # Get the last alarm
|
||||||
|
if self.api_client.delete_alarm(alarm["id"]):
|
||||||
|
self.logger.log_system_error(f"Deleted Alarm ID: {alarm['id']}")
|
||||||
|
self.alarm_logic.refresh_alarms()
|
||||||
|
else:
|
||||||
|
self.show_error("Failed to delete alarm")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.log_system_error(f"Delete alarm error: {str(e)}", exc_info=True)
|
||||||
|
self.show_error(f"Delete error: {str(e)}")
|
||||||
|
|
||||||
|
def handle_list_alarms_input(self, key):
|
||||||
|
try:
|
||||||
|
if key == 27: # Escape
|
||||||
|
self.selected_menu = 0
|
||||||
|
self.logger.log_system_error("List Alarms menu cancelled")
|
||||||
|
elif key == ord('d'):
|
||||||
|
self.logger.log_system_error("Attempting to delete last alarm")
|
||||||
|
self.delete_selected_alarm()
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.log_system_error(f"Error in list alarms input: {str(e)}", exc_info=True)
|
||||||
|
|
||||||
|
def draw_main_clock(self):
|
||||||
|
self.ncurses_ui.draw_main_clock()
|
||||||
|
|
||||||
|
def draw_add_alarm(self):
|
||||||
|
self.ncurses_ui.draw_add_alarm()
|
||||||
|
|
||||||
|
def draw_list_alarms(self):
|
||||||
|
self.ncurses_ui.draw_list_alarms()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
# Start alarm checking thread
|
||||||
|
self.ncurses_threads.start_threads()
|
||||||
|
self.logger.log_system_error("Alarm checking threads started")
|
||||||
|
|
||||||
|
while self.running:
|
||||||
|
try:
|
||||||
|
# Clear and redraw the screen
|
||||||
|
self.stdscr.erase()
|
||||||
|
|
||||||
|
if self.selected_menu == 0:
|
||||||
|
self.draw_main_clock()
|
||||||
|
elif self.selected_menu == 1:
|
||||||
|
self.draw_add_alarm()
|
||||||
|
elif self.selected_menu == 2:
|
||||||
|
self.draw_list_alarms()
|
||||||
|
|
||||||
|
self.stdscr.refresh()
|
||||||
|
|
||||||
|
# Handle input with timeout
|
||||||
|
key = self.stdscr.getch()
|
||||||
|
if key != -1: # Key was pressed
|
||||||
|
if self.selected_menu == 0:
|
||||||
|
if key == ord('q'):
|
||||||
|
self.logger.log_system_error("Application quit requested")
|
||||||
|
self.alarm_logic.stop_alarm()
|
||||||
|
break
|
||||||
|
elif key == ord('a'):
|
||||||
|
self.selected_menu = 1
|
||||||
|
self.logger.log_system_error("Switched to Add Alarm menu")
|
||||||
|
elif key == ord('l'):
|
||||||
|
self.selected_menu = 2
|
||||||
|
self.logger.log_system_error("Switched to List Alarms menu")
|
||||||
|
elif key == ord('s'):
|
||||||
|
self.logger.log_system_error("Alarm stopped")
|
||||||
|
self.alarm_logic.stop_alarm()
|
||||||
|
elif key == ord('z'):
|
||||||
|
self.logger.log_alarm_snoozed(
|
||||||
|
"current_alarm",
|
||||||
|
self.snooze_minutes
|
||||||
|
)
|
||||||
|
self.alarm_logic.snooze_alarm()
|
||||||
|
elif self.selected_menu == 1:
|
||||||
|
self.handle_add_alarm_input(key)
|
||||||
|
elif self.selected_menu == 2:
|
||||||
|
self.handle_list_alarms_input(key)
|
||||||
|
|
||||||
|
except curses.error as e:
|
||||||
|
self.logger.log_system_error(f"Curses error: {str(e)}", exc_info=True)
|
||||||
|
self.running = False
|
||||||
|
raise Exception(f"Curses error: {str(e)}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.log_system_error(f"Unhandled error in main run loop: {str(e)}", exc_info=True)
|
||||||
|
raise
|
30
alert_clients/ncurses_client/ncurses_threads.py
Normal file
30
alert_clients/ncurses_client/ncurses_threads.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import time
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
class NcursesThreads:
|
||||||
|
def __init__(self, alarm_logic):
|
||||||
|
self.alarm_logic = alarm_logic
|
||||||
|
self.running = True
|
||||||
|
|
||||||
|
def alarm_check_thread(self):
|
||||||
|
"""Thread for continuously checking alarms."""
|
||||||
|
while self.running:
|
||||||
|
try:
|
||||||
|
self.alarm_logic.check_alarms()
|
||||||
|
time.sleep(1)
|
||||||
|
except Exception as e:
|
||||||
|
# Handle any errors during alarm checking
|
||||||
|
print(f"Alarm check error: {str(e)}")
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
def start_threads(self):
|
||||||
|
"""Starts the necessary threads for ncurses client."""
|
||||||
|
self.alarm_thread = Thread(target=self.alarm_check_thread)
|
||||||
|
self.alarm_thread.daemon = True
|
||||||
|
self.alarm_thread.start()
|
||||||
|
|
||||||
|
def stop_threads(self):
|
||||||
|
"""Stops all running threads."""
|
||||||
|
self.running = False
|
||||||
|
if self.alarm_thread.is_alive():
|
||||||
|
self.alarm_thread.join()
|
166
alert_clients/ncurses_client/ncurses_ui.py
Normal file
166
alert_clients/ncurses_client/ncurses_ui.py
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
import curses
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
from big_digits import BIG_DIGITS
|
||||||
|
|
||||||
|
class NcursesUI:
|
||||||
|
def __init__(self, stdscr):
|
||||||
|
# Initialize curses
|
||||||
|
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)
|
||||||
|
self.stdscr.keypad(1)
|
||||||
|
self.stdscr.timeout(100)
|
||||||
|
|
||||||
|
def show_error(self, message):
|
||||||
|
height, width = self.stdscr.getmaxyx()
|
||||||
|
self.stdscr.attron(curses.color_pair(3))
|
||||||
|
self.stdscr.addstr(height-1, 0, f"ERROR: {message}"[:width-1])
|
||||||
|
self.stdscr.attroff(curses.color_pair(3))
|
||||||
|
self.stdscr.refresh()
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
def draw_big_digit(self, y, x, digit):
|
||||||
|
patterns = BIG_DIGITS[digit]
|
||||||
|
for i, line in enumerate(patterns):
|
||||||
|
self.stdscr.addstr(y + i, x, line)
|
||||||
|
|
||||||
|
def draw_big_time(self, current_time):
|
||||||
|
height, width = self.stdscr.getmaxyx()
|
||||||
|
time_str = current_time.strftime("%H:%M:%S")
|
||||||
|
|
||||||
|
# Calculate starting position to center the big clock
|
||||||
|
digit_width = 14 # Width of each digit pattern including spacing
|
||||||
|
total_width = digit_width * len(time_str)
|
||||||
|
start_x = (width - total_width) // 2
|
||||||
|
start_y = (height - 7) // 2 - 4 # Move up a bit to make room for date
|
||||||
|
|
||||||
|
self.stdscr.attron(curses.color_pair(1))
|
||||||
|
for i, digit in enumerate(time_str):
|
||||||
|
self.draw_big_digit(start_y, start_x + i * digit_width, digit)
|
||||||
|
self.stdscr.attroff(curses.color_pair(1))
|
||||||
|
|
||||||
|
def draw_main_clock(self):
|
||||||
|
current_time = datetime.now()
|
||||||
|
time_str = current_time.strftime("%H:%M:%S")
|
||||||
|
date_str = current_time.strftime("%Y-%m-%d")
|
||||||
|
|
||||||
|
# Get terminal dimensions
|
||||||
|
height, width = self.stdscr.getmaxyx()
|
||||||
|
|
||||||
|
# Draw big time
|
||||||
|
self.draw_big_time(current_time)
|
||||||
|
|
||||||
|
# Draw date
|
||||||
|
date_x = width // 2 - len(date_str) // 2
|
||||||
|
date_y = height // 2 + 4 # Below the big clock
|
||||||
|
|
||||||
|
self.stdscr.attron(curses.color_pair(2))
|
||||||
|
self.stdscr.addstr(date_y, date_x, date_str)
|
||||||
|
self.stdscr.attroff(curses.color_pair(2))
|
||||||
|
|
||||||
|
# Draw menu options
|
||||||
|
menu_str = "A: Add Alarm L: List Alarms S: Stop Z: Snooze Q: Quit"
|
||||||
|
menu_x = width // 2 - len(menu_str) // 2
|
||||||
|
self.stdscr.addstr(height - 2, menu_x, menu_str)
|
||||||
|
|
||||||
|
# Show alarm/snooze status
|
||||||
|
if self.current_alarm_process:
|
||||||
|
status_str = "⏰ ALARM ACTIVE - Press 'S' to stop or 'Z' to snooze"
|
||||||
|
status_x = width // 2 - len(status_str) // 2
|
||||||
|
self.stdscr.attron(curses.color_pair(3))
|
||||||
|
self.stdscr.addstr(height - 4, status_x, status_str)
|
||||||
|
self.stdscr.attroff(curses.color_pair(3))
|
||||||
|
elif self.snooze_until:
|
||||||
|
snooze_str = f"💤 Snoozed until {self.snooze_until.strftime('%H:%M')}"
|
||||||
|
snooze_x = width // 2 - len(snooze_str) // 2
|
||||||
|
self.stdscr.attron(curses.color_pair(2))
|
||||||
|
self.stdscr.addstr(height - 4, snooze_x, snooze_str)
|
||||||
|
self.stdscr.attroff(curses.color_pair(2))
|
||||||
|
|
||||||
|
def draw_add_alarm(self):
|
||||||
|
height, width = self.stdscr.getmaxyx()
|
||||||
|
|
||||||
|
form_y = height // 2 - 3
|
||||||
|
self.stdscr.addstr(form_y, width // 2 - 10, "New Alarm")
|
||||||
|
|
||||||
|
# Time selection
|
||||||
|
hour_str = f"{self.new_alarm_hour:02d}"
|
||||||
|
minute_str = f"{self.new_alarm_minute:02d}"
|
||||||
|
|
||||||
|
# Highlight selected field
|
||||||
|
if self.new_alarm_selected == 0:
|
||||||
|
self.stdscr.attron(curses.color_pair(2))
|
||||||
|
self.stdscr.addstr(form_y + 1, width // 2 - 2, hour_str)
|
||||||
|
if self.new_alarm_selected == 0:
|
||||||
|
self.stdscr.attroff(curses.color_pair(2))
|
||||||
|
|
||||||
|
self.stdscr.addstr(form_y + 1, width // 2, ":")
|
||||||
|
|
||||||
|
if self.new_alarm_selected == 1:
|
||||||
|
self.stdscr.attron(curses.color_pair(2))
|
||||||
|
self.stdscr.addstr(form_y + 1, width // 2 + 1, minute_str)
|
||||||
|
if self.new_alarm_selected == 1:
|
||||||
|
self.stdscr.attroff(curses.color_pair(2))
|
||||||
|
|
||||||
|
# Date selection
|
||||||
|
date_str = "No specific date" if not self.new_alarm_date else self.new_alarm_date.strftime("%Y-%m-%d")
|
||||||
|
if self.new_alarm_selected == 2:
|
||||||
|
self.stdscr.attron(curses.color_pair(2))
|
||||||
|
self.stdscr.addstr(form_y + 3, width // 2 - len(date_str) // 2, date_str)
|
||||||
|
if self.new_alarm_selected == 2:
|
||||||
|
self.stdscr.attroff(curses.color_pair(2))
|
||||||
|
|
||||||
|
# Weekday selection
|
||||||
|
weekday_str = "Repeat: " + " ".join(
|
||||||
|
self.weekday_names[i] if i in self.new_alarm_weekdays else "___"
|
||||||
|
for i in range(7)
|
||||||
|
)
|
||||||
|
if self.new_alarm_selected == 3:
|
||||||
|
self.stdscr.attron(curses.color_pair(2))
|
||||||
|
self.stdscr.addstr(form_y + 4, width // 2 - len(weekday_str) // 2, weekday_str)
|
||||||
|
if self.new_alarm_selected == 3:
|
||||||
|
self.stdscr.attroff(curses.color_pair(2))
|
||||||
|
|
||||||
|
# Instructions
|
||||||
|
if self.new_alarm_selected < 2:
|
||||||
|
self.stdscr.addstr(height - 2, 2, "↑↓: Change value ←→: Switch field Enter: Save Esc: Cancel")
|
||||||
|
elif self.new_alarm_selected == 2:
|
||||||
|
self.stdscr.addstr(height - 2, 2, "↑↓: Change date Space: Clear date Enter: Save Esc: Cancel")
|
||||||
|
else: # weekday selection
|
||||||
|
self.stdscr.addstr(height - 2, 2, "←→: Select day Space: Toggle Enter: Save Esc: Cancel")
|
||||||
|
|
||||||
|
def draw_list_alarms(self):
|
||||||
|
height, width = self.stdscr.getmaxyx()
|
||||||
|
|
||||||
|
self.stdscr.addstr(2, width // 2 - 5, "Alarms")
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.alarms = self.api_client.get_alarms()
|
||||||
|
for i, alarm in enumerate(self.alarms):
|
||||||
|
# Parse time from the time string
|
||||||
|
time_parts = alarm['time'].split(':')
|
||||||
|
time_str = f"{time_parts[0]}:{time_parts[1]}"
|
||||||
|
|
||||||
|
# Add repeat rule information
|
||||||
|
if 'repeat_rule' in alarm:
|
||||||
|
if alarm['repeat_rule']['type'] == 'once' and 'date' in alarm['repeat_rule']:
|
||||||
|
time_str += f" on {alarm['repeat_rule']['date']}"
|
||||||
|
elif alarm['repeat_rule']['type'] == 'weekly' and 'days' in alarm['repeat_rule']:
|
||||||
|
weekdays = [self.weekday_names[d] for d in alarm['repeat_rule']['days']]
|
||||||
|
time_str += f" every {', '.join(weekdays)}"
|
||||||
|
|
||||||
|
status = "✓" if alarm['enabled'] else "✗"
|
||||||
|
display_str = f"{status} {time_str}"
|
||||||
|
|
||||||
|
self.stdscr.addstr(4 + i, width // 2 - len(display_str) // 2, display_str)
|
||||||
|
|
||||||
|
if not self.alarms:
|
||||||
|
self.stdscr.addstr(4, width // 2 - 7, "No alarms set")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.show_error(f"Failed to display alarms: {str(e)}")
|
||||||
|
|
||||||
|
self.stdscr.addstr(height - 2, 2, "D: Delete alarm Esc: Back")
|
Loading…
x
Reference in New Issue
Block a user