diff --git a/install.sh b/install.sh deleted file mode 100644 index 1435e7e..0000000 --- a/install.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -echo "Attempting install..." -pip install tk -pip install pathlib -pip install requests -pip install datetime -pip install OAuth2 -echo "Installation complete" \ No newline at end of file diff --git a/run_dev.sh b/run_dev.sh new file mode 100755 index 0000000..e190a06 --- /dev/null +++ b/run_dev.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +set -a +source "$(dirname "$0")/.env" +set +a + +for arg in "$@"; do + if [ "$arg" = "--dev" ] || [ "$arg" = "-d" ]; then + export DEV_MODE=1 + break + fi +done + +output_file="log.txt" + +echo "" >> "$output_file" + +date "+%Y-%m-%d %H:%M:%S" >> "$output_file" + +python src/main.py "$@" 2>&1 | tee -a log.txt diff --git a/src/api/__init__.py b/src/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/api/_client.py b/src/api/_client.py new file mode 100644 index 0000000..a1f848e --- /dev/null +++ b/src/api/_client.py @@ -0,0 +1,12 @@ +import logging +import time + +import requests + + +def _req(method, url, **kwargs): + start = time.time() + resp = requests.request(method, url, **kwargs) + ms = (time.time() - start) * 1000 + logging.info(f"[CLIENT] {method.upper()} {url} -> {resp.status_code} ({ms:.0f}ms)") + return resp diff --git a/src/sheets.py b/src/api/client.py similarity index 71% rename from src/sheets.py rename to src/api/client.py index 307f2af..dc81408 100644 --- a/src/sheets.py +++ b/src/api/client.py @@ -2,18 +2,10 @@ import sys import time -import requests - from config import API_BASE_URL +from api._client import _req -def _req(method, url, **kwargs): - start = time.time() - resp = requests.request(method, url, **kwargs) - ms = (time.time() - start) * 1000 - logging.info(f"[CLIENT] {method.upper()} {url} -> {resp.status_code} ({ms:.0f}ms)") - return resp - def check_api_health(retries=3, delay=3): logging.info(API_BASE_URL) @@ -31,7 +23,7 @@ def check_api_health(retries=3, delay=3): sys.exit(1) -class SheetManager: +class ApiClient: def checkin_by_uuid(self, uuid): try: resp = _req("GET", f"{API_BASE_URL}/check-in/uuid/{uuid}", timeout=10) @@ -58,26 +50,20 @@ def set_traffic_light(self, color): def get_traffic_light(self): try: - resp = requests.get(f"{API_BASE_URL}/traffic-light", timeout=5) + resp = _req("GET", f"{API_BASE_URL}/traffic-light", timeout=5) return resp.json().get("color", "off") except Exception as e: logging.error(f"Error getting traffic light: {e}") return "off" - def create_account(self, first_name, last_name, email, pid, rfid): + def create_account(self, rfid, *, barcode=None, pid=None): try: - resp = _req( - "POST", - f"{API_BASE_URL}/accounts", - json={ - "first_name": first_name, - "last_name": last_name, - "email": email, - "pid": pid, - "rfid": rfid, - }, - timeout=30, - ) + payload = {"rfid": rfid} + if barcode: + payload["barcode"] = barcode + if pid: + payload["pid"] = pid + resp = _req("POST", f"{API_BASE_URL}/accounts", json=payload, timeout=30) resp.raise_for_status() return resp.json() except Exception as e: diff --git a/src/api/traffic_light_api.py b/src/api/traffic_light_api.py new file mode 100644 index 0000000..943bcc1 --- /dev/null +++ b/src/api/traffic_light_api.py @@ -0,0 +1,44 @@ +import threading + +from api.client import ApiClient +from hardware.traffic_light import TrafficLight + + +class TrafficLightApi: + def __init__(self, light: TrafficLight, sheets: ApiClient): + self._light = light + self._sheets = sheets + + @property + def connected(self) -> bool: + return self._light.connected + + def drive(self, color: str) -> None: + """Directly set the physical traffic light without posting to the API.""" + if color == "red": + self._light.set_red() + elif color == "green": + self._light.set_green() + elif color == "yellow": + self._light.set_yellow() + else: + self._light.set_off() + + def _post(self, color: str) -> None: + threading.Thread( + target=self._sheets.set_traffic_light, + args=(color,), + daemon=True, + ).start() + + def request_red(self) -> None: + self._post("red") + + def request_green(self) -> None: + self._post("green") + + def request_yellow(self) -> None: + self._post("yellow") + + def request_off(self) -> None: + self._post("off") diff --git a/src/app_context.py b/src/app_context.py new file mode 100644 index 0000000..904de3b --- /dev/null +++ b/src/app_context.py @@ -0,0 +1,34 @@ +import threading + +from api.client import ApiClient +from api.traffic_light_api import TrafficLightApi +from hardware.traffic_light import TrafficLight + + +class AppContext: + def __init__(self, sheets: ApiClient, traffic_light: TrafficLightApi): + self.sheets = sheets + self.traffic_light = traffic_light + self.window = None + self.nav = None + self.check_in = None + self.account = None + self._rfid_lock = threading.Lock() + self._rfid: str = "" + + @property + def rfid(self) -> str: + with self._rfid_lock: + return self._rfid + + @rfid.setter + def rfid(self, value: str) -> None: + with self._rfid_lock: + self._rfid = value + + @classmethod + def create(cls, traffic_usb_id=None) -> "AppContext": + sheets = ApiClient() + light = TrafficLight(traffic_usb_id) + traffic = TrafficLightApi(light, sheets) + return cls(sheets, traffic) diff --git a/src/assets/check_in_no_id_assets/button_1.png b/src/assets/check_in_manual/button_check_in.png similarity index 100% rename from src/assets/check_in_no_id_assets/button_1.png rename to src/assets/check_in_manual/button_check_in.png diff --git a/src/assets/check_in_no_id_assets/image_2.png b/src/assets/check_in_no_id_assets/image_2.png deleted file mode 100644 index eb574c4..0000000 Binary files a/src/assets/check_in_no_id_assets/image_2.png and /dev/null differ diff --git a/src/assets/main_page_assets/image_4.png b/src/assets/check_in_rfid/icon_check_in.png similarity index 100% rename from src/assets/main_page_assets/image_4.png rename to src/assets/check_in_rfid/icon_check_in.png diff --git a/src/assets/no_acc_no_waiver_swipe_assets/button_1.png b/src/assets/create_account_barcode/button_fill_manually.png similarity index 100% rename from src/assets/no_acc_no_waiver_swipe_assets/button_1.png rename to src/assets/create_account_barcode/button_fill_manually.png diff --git a/src/assets/manual_fill_assets/image_2.png b/src/assets/create_account_barcode/outline_1.png similarity index 100% rename from src/assets/manual_fill_assets/image_2.png rename to src/assets/create_account_barcode/outline_1.png diff --git a/src/assets/manual_fill_assets/image_3.png b/src/assets/create_account_barcode/outline_2.png similarity index 100% rename from src/assets/manual_fill_assets/image_3.png rename to src/assets/create_account_barcode/outline_2.png diff --git a/src/assets/no_acc_no_waiver_swipe_assets/image_2.png b/src/assets/create_account_manual/outline_1.png similarity index 100% rename from src/assets/no_acc_no_waiver_swipe_assets/image_2.png rename to src/assets/create_account_manual/outline_1.png diff --git a/src/assets/no_acc_no_waiver_swipe_assets/image_3.png b/src/assets/create_account_manual/outline_2.png similarity index 100% rename from src/assets/no_acc_no_waiver_swipe_assets/image_3.png rename to src/assets/create_account_manual/outline_2.png diff --git a/src/assets/manual_fill_assets/button_1.png b/src/assets/create_account_manual/register.png similarity index 100% rename from src/assets/manual_fill_assets/button_1.png rename to src/assets/create_account_manual/register.png diff --git a/src/assets/manual_fill_assets/image_4.png b/src/assets/manual_fill_assets/image_4.png deleted file mode 100644 index 37f2074..0000000 Binary files a/src/assets/manual_fill_assets/image_4.png and /dev/null differ diff --git a/src/assets/manual_fill_assets/image_5.png b/src/assets/manual_fill_assets/image_5.png deleted file mode 100644 index 37f2074..0000000 Binary files a/src/assets/manual_fill_assets/image_5.png and /dev/null differ diff --git a/src/assets/manual_fill_assets/image_6.png b/src/assets/manual_fill_assets/image_6.png deleted file mode 100644 index 581d067..0000000 Binary files a/src/assets/manual_fill_assets/image_6.png and /dev/null differ diff --git a/src/assets/manual_fill_assets/image_7.png b/src/assets/manual_fill_assets/image_7.png deleted file mode 100644 index 581d067..0000000 Binary files a/src/assets/manual_fill_assets/image_7.png and /dev/null differ diff --git a/src/assets/manual_fill_assets/image_8.png b/src/assets/manual_fill_assets/image_8.png deleted file mode 100644 index 581d067..0000000 Binary files a/src/assets/manual_fill_assets/image_8.png and /dev/null differ diff --git a/src/assets/manual_fill_assets/image_9.png b/src/assets/manual_fill_assets/image_9.png deleted file mode 100644 index 581d067..0000000 Binary files a/src/assets/manual_fill_assets/image_9.png and /dev/null differ diff --git a/src/assets/no_acc_no_waiver_swipe_assets/image_4.png b/src/assets/no_acc_no_waiver_swipe_assets/image_4.png deleted file mode 100644 index 37f2074..0000000 Binary files a/src/assets/no_acc_no_waiver_swipe_assets/image_4.png and /dev/null differ diff --git a/src/assets/no_acc_no_waiver_swipe_assets/image_5.png b/src/assets/no_acc_no_waiver_swipe_assets/image_5.png deleted file mode 100644 index 37f2074..0000000 Binary files a/src/assets/no_acc_no_waiver_swipe_assets/image_5.png and /dev/null differ diff --git a/src/assets/qr_codes_assets/image_5.png b/src/assets/qr_codes/qr_waiver.png similarity index 100% rename from src/assets/qr_codes_assets/image_5.png rename to src/assets/qr_codes/qr_waiver.png diff --git a/src/assets/qr_codes_assets/image_4.png b/src/assets/qr_codes/qr_website.png similarity index 100% rename from src/assets/qr_codes_assets/image_4.png rename to src/assets/qr_codes/qr_website.png diff --git a/src/assets/qr_codes_assets/image_3.png b/src/assets/qr_codes_assets/image_3.png deleted file mode 100644 index 7eeb581..0000000 Binary files a/src/assets/qr_codes_assets/image_3.png and /dev/null differ diff --git a/src/assets/shared/image_1.png b/src/assets/shared/background_main.png similarity index 100% rename from src/assets/shared/image_1.png rename to src/assets/shared/background_main.png diff --git a/src/assets/main_page_assets/image_3.png b/src/assets/shared/button_generic.png similarity index 100% rename from src/assets/main_page_assets/image_3.png rename to src/assets/shared/button_generic.png diff --git a/src/assets/check_in_no_id_assets/image_3.png b/src/assets/shared/field.png similarity index 100% rename from src/assets/check_in_no_id_assets/image_3.png rename to src/assets/shared/field.png diff --git a/src/assets/acc_no_waiver_swipe_assets/image_5.png b/src/assets/shared/icon_checked_box.png similarity index 100% rename from src/assets/acc_no_waiver_swipe_assets/image_5.png rename to src/assets/shared/icon_checked_box.png diff --git a/src/assets/qr_codes_assets/image_6.png b/src/assets/shared/icon_home.png similarity index 100% rename from src/assets/qr_codes_assets/image_6.png rename to src/assets/shared/icon_home.png diff --git a/src/assets/acc_no_waiver_swipe_assets/image_6.png b/src/assets/shared/icon_unchecked_box.png similarity index 100% rename from src/assets/acc_no_waiver_swipe_assets/image_6.png rename to src/assets/shared/icon_unchecked_box.png diff --git a/src/assets/shared/image_2.png b/src/assets/shared/outline_full.png similarity index 100% rename from src/assets/shared/image_2.png rename to src/assets/shared/outline_full.png diff --git a/src/assets/acc_no_waiver_swipe_assets/button_1.png b/src/assets/sign_waiver/button_done_scanning.png similarity index 100% rename from src/assets/acc_no_waiver_swipe_assets/button_1.png rename to src/assets/sign_waiver/button_done_scanning.png diff --git a/src/assets/acc_no_waiver_swipe_assets/image_2.png b/src/assets/sign_waiver/outline_1.png similarity index 100% rename from src/assets/acc_no_waiver_swipe_assets/image_2.png rename to src/assets/sign_waiver/outline_1.png diff --git a/src/assets/acc_no_waiver_swipe_assets/image_3.png b/src/assets/sign_waiver/outline_2.png similarity index 100% rename from src/assets/acc_no_waiver_swipe_assets/image_3.png rename to src/assets/sign_waiver/outline_2.png diff --git a/src/assets/acc_no_waiver_swipe_assets/image_4.png b/src/assets/sign_waiver/outline_3.png similarity index 100% rename from src/assets/acc_no_waiver_swipe_assets/image_4.png rename to src/assets/sign_waiver/outline_3.png diff --git a/src/assets/acc_no_waiver_swipe_assets/image_7.png b/src/assets/sign_waiver/qr_waiver.png similarity index 100% rename from src/assets/acc_no_waiver_swipe_assets/image_7.png rename to src/assets/sign_waiver/qr_waiver.png diff --git a/src/assets/waiver_no_acc_swipe_assets/button_1.png b/src/assets/waiver_no_acc_swipe_assets/button_1.png deleted file mode 100644 index 6771e90..0000000 Binary files a/src/assets/waiver_no_acc_swipe_assets/button_1.png and /dev/null differ diff --git a/src/assets/waiver_no_acc_swipe_assets/image_2.png b/src/assets/waiver_no_acc_swipe_assets/image_2.png deleted file mode 100644 index 45147ba..0000000 Binary files a/src/assets/waiver_no_acc_swipe_assets/image_2.png and /dev/null differ diff --git a/src/assets/waiver_no_acc_swipe_assets/image_3.png b/src/assets/waiver_no_acc_swipe_assets/image_3.png deleted file mode 100644 index d42add7..0000000 Binary files a/src/assets/waiver_no_acc_swipe_assets/image_3.png and /dev/null differ diff --git a/src/assets/waiver_no_acc_swipe_assets/image_4.png b/src/assets/waiver_no_acc_swipe_assets/image_4.png deleted file mode 100644 index 37f2074..0000000 Binary files a/src/assets/waiver_no_acc_swipe_assets/image_4.png and /dev/null differ diff --git a/src/assets/waiver_no_acc_swipe_assets/image_5.png b/src/assets/waiver_no_acc_swipe_assets/image_5.png deleted file mode 100644 index 19cd880..0000000 Binary files a/src/assets/waiver_no_acc_swipe_assets/image_5.png and /dev/null differ diff --git a/src/controllers/__init__.py b/src/controllers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/controllers/account_controller.py b/src/controllers/account_controller.py new file mode 100644 index 0000000..7fd9de2 --- /dev/null +++ b/src/controllers/account_controller.py @@ -0,0 +1,39 @@ +import tkinter +import logging + + +class AccountController: + def __init__(self, ctx): + self.ctx = ctx + + def create_account_from_barcode(self, barcode): + self._create(barcode=barcode) + + def create_account_from_pid(self, pid): + self._create(pid=pid) + + def _create(self, *, barcode=None, pid=None): + canvas = self.ctx.window.canvas + inProgress = tkinter.Label( + canvas, + text="Account creation in progress!", + bg="#153246", fg="white", font=("Arial", 25), + ) + inProgress.place(relx=0.5, rely=0.87, anchor="center") + self.ctx.window.update() + + result = self.ctx.sheets.create_account(self.ctx.rfid, barcode=barcode, pid=pid) + inProgress.destroy() + + if result is None: + error = tkinter.Label( + canvas, + text="ERROR! Could not create account, please try manually.", + bg="#153246", fg="white", font=("Arial", 20), + ) + error.place(relx=0.5, rely=0.87, anchor="center") + error.after(3000, lambda: error.destroy()) + return + + logging.info("Account creation succeeded") + self.ctx.nav.pop() diff --git a/src/controllers/check_in_controller.py b/src/controllers/check_in_controller.py new file mode 100644 index 0000000..c0abd31 --- /dev/null +++ b/src/controllers/check_in_controller.py @@ -0,0 +1,54 @@ +import logging +from tkinter import Label + +from screens.user_welcome import UserWelcome + + +class CheckInController: + def __init__(self, ctx): + self.ctx = ctx + + def handle_by_uuid(self, tag): + # Called from background thread — defer to main thread. + self.ctx.window.after( + 0, lambda: self._run_check_in(tag, self.ctx.sheets.checkin_by_uuid) + ) + + def handle_by_pid(self, pid): + self._run_check_in(pid, self.ctx.sheets.checkin_by_pid) + + def _run_check_in(self, identifier, check_fn, welcome_message="Welcome back"): + result = check_fn(identifier) + status = result.get("status") + + if status == "api_error": + logging.error("API error during check-in") + self.ctx.traffic_light.request_red() + error_label = Label( + self.ctx.window.canvas, + text="System error, please let staff know.", + bg="#153246", fg="white", font=("Arial", 25), + ) + error_label.place(relx=0.5, rely=0.1, anchor="center") + error_label.after(4000, error_label.destroy) + return + + if status == "no_account": + logging.info(f"No account found for {identifier}") + self.ctx.traffic_light.request_red() + self.ctx.nav.go_to_create_account( + on_done=lambda: self._run_check_in( + identifier, check_fn, welcome_message="Thank you for registering" + ) + ) + return + + if status == "no_waiver": + logging.info(f"No waiver for {identifier}") + self.ctx.traffic_light.request_yellow() + self.ctx.nav.go_to_sign_waiver() + return + + logging.info(f"Check-in successful: {result['name']}") + self.ctx.traffic_light.request_green() + self.ctx.nav.get_frame(UserWelcome).display_name(result["name"], welcome_message) diff --git a/src/controllers/navigation_controller.py b/src/controllers/navigation_controller.py new file mode 100644 index 0000000..669bd3b --- /dev/null +++ b/src/controllers/navigation_controller.py @@ -0,0 +1,129 @@ +import uuid + +from screens.check_in_rfid import CheckInRFID +from screens.transition_screen import TransitionScreen +from screens.create_account_barcode import CreateAccountBarcode +from screens.create_account_manual import CreateAccountManual +from screens.sign_waiver import SignWaiver +from screens.check_in_manual import CheckInManual +from screens.qr_codes import QRCodes +from screens.user_welcome import UserWelcome + + +class NavigationController: + def __init__(self, window, ctx, dev_mode=False): + self.ctx = ctx + self._window = window + self._frames = {} + self._curr = None + self._frame_uuid = uuid.uuid4().hex + self._on_done_stack = [] + self._dev_overlay = None + + self._timeouts = { + SignWaiver: 30000, + QRCodes: 30000, + } + + for F in ( + CheckInRFID, + TransitionScreen, + CreateAccountBarcode, + CreateAccountManual, + SignWaiver, + CheckInManual, + QRCodes, + UserWelcome, + ): + self._frames[F] = F(window.canvas, self) + + if dev_mode: + from screens.components.dev_overlay import DevOverlay + self._dev_overlay = DevOverlay(window.canvas, self) + + self.show_frame(CheckInRFID) + + # ------------------------------------------------------------------ + # Core frame switching + # ------------------------------------------------------------------ + + def show_frame(self, screen_class): + if self._curr is not None: + self._frames[self._curr].hide() + self._curr = screen_class + self._frame_uuid = uuid.uuid4().hex + self._frames[screen_class].show() + + if self._dev_overlay is not None: + self._dev_overlay.update(screen_class) + + if screen_class in self._timeouts: + uid = self._frame_uuid + self._window.after( + self._timeouts[screen_class], + lambda: self._on_timeout(uid), + ) + + def get_frame(self, screen_class): + return self._frames[screen_class] + + def get_curr_frame(self): + return self._curr + + def after(self, ms, fn): + self._window.after(ms, fn) + + # ------------------------------------------------------------------ + # Stack-based flow + # ------------------------------------------------------------------ + + def push(self, screen_class, on_done=None): + """Show screen_class and register a continuation to run when pop() is called.""" + self._on_done_stack.append(on_done) + self.show_frame(screen_class) + + def pop(self): + """Signal that the current screen is done; run the stored continuation.""" + cb = self._on_done_stack.pop() if self._on_done_stack else None + if cb: + cb() + else: + self.back_to_main() + + # ------------------------------------------------------------------ + # Named navigations + # ------------------------------------------------------------------ + + def back_to_main(self): + self._on_done_stack.clear() + self.ctx.rfid = "" + self.ctx.traffic_light.request_off() + self.show_frame(CheckInRFID) + + def go_to_no_id(self): + self.get_frame(CheckInManual).clear_entries() + self.show_frame(CheckInManual) + + def go_to_create_account_manual(self): + self.get_frame(CreateAccountManual).clear_entries() + self.show_frame(CreateAccountManual) + + def go_to_create_account(self, on_done): + self.get_frame(TransitionScreen).display( + "Looks like you don't have an account,\nlet's set one up!" + ) + self._window.after(3000, lambda: self.push(CreateAccountBarcode, on_done=on_done)) + + def go_to_sign_waiver(self): + self.get_frame(TransitionScreen).display( + "Looks like you haven't signed\nthe waiver yet,\nlet's fix that!" + ) + self._window.after(3000, lambda: self.show_frame(SignWaiver)) + + # ------------------------------------------------------------------ + # Internal + # ------------------------------------------------------------------ + + def _on_timeout(self, uid): + if uid == self._frame_uuid: + self.back_to_main() diff --git a/src/controllers/rfid_reader_controller.py b/src/controllers/rfid_reader_controller.py new file mode 100644 index 0000000..4c562d9 --- /dev/null +++ b/src/controllers/rfid_reader_controller.py @@ -0,0 +1,99 @@ +import time +import socket +import logging +from tkinter import Label +from threading import Thread +from screens.create_account_manual import CreateAccountManual + + +class RfidReaderController: + def __init__(self, ctx): + self.ctx = ctx + self._no_wifi_shown = False + + def start(self, reader): + thread = Thread(target=self._run, args=(reader,)) + thread.start() + if self.ctx.traffic_light.connected: + poller = Thread(target=self._poll_traffic_light, daemon=True) + poller.start() + + def _run(self, reader): + logging.info("Now reading ID cards") + last_tag = 0 + last_time = 0 + scanner_error = False + while True: + if scanner_error: + time.sleep(0.1) + if reader.reconnect(): + logging.info("Card reader reconnected") + scanner_error = False + continue + + try: + in_waiting = reader.get_ser_in_waiting() + except OSError as e: + if not scanner_error: + logging.error("Card reader disconnected, disabling until reconnection: %s", e) + scanner_error = True + continue + + if in_waiting >= 14: + if not self._is_connected(): + logging.info("ERROR wifi is not connected") + if not self._no_wifi_shown: + self._no_wifi_shown = True + no_wifi = Label( + self.ctx.window.canvas, + text="ERROR! Connection cannot be established, please let staff know.", + bg="#153246", fg="white", font=("Arial", 25), + ) + no_wifi.place(relx=0.5, rely=0.1, anchor="center") + no_wifi.after(4000, lambda: self._destroy_wifi_error(no_wifi)) + continue + + self.ctx.nav.get_frame(CreateAccountManual).clear_entries() + tag = reader.grab_rfid() + + if " " in tag: + continue + + if tag == last_tag and not reader.can_scan_again(last_time): + logging.debug("Suppressing repeat scan") + continue + + s_reason = reader.check_rfid(tag) + + if s_reason != "good": + logging.debug(s_reason) + continue + else: + logging.debug("RFID Check Succeeded") + + self.ctx.rfid = tag + self.ctx.check_in.handle_by_uuid(tag) + + last_tag = tag + last_time = time.time() + + def _poll_traffic_light(self): + last_color = None + while True: + time.sleep(0.1) + color = self.ctx.sheets.get_traffic_light() + if color != last_color: + last_color = color + self.ctx.traffic_light.drive(color) + + def _is_connected(self, host="8.8.8.8", port=53, timeout=3): + try: + socket.setdefaulttimeout(timeout) + socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((host, port)) + return True + except socket.error: + return False + + def _destroy_wifi_error(self, label): + label.destroy() + self._no_wifi_shown = False diff --git a/src/controllers/swipe_controller.py b/src/controllers/swipe_controller.py new file mode 100644 index 0000000..d3c26d6 --- /dev/null +++ b/src/controllers/swipe_controller.py @@ -0,0 +1,48 @@ +import tkinter +import logging +from screens.create_account_barcode import CreateAccountBarcode +from screens.create_account_manual import CreateAccountManual + + +class SwipeController: + def __init__(self, ctx): + self.ctx = ctx + self._id_string = "" + self._swipe_error_shown = False + + def _id_vet(self, id_check): + if any(i.isalpha() for i in id_check): + return "bad" + if len(id_check) >= 16: + return "bad" + return "good" + + def keyboard_press(self, key): + curr_frame = self.ctx.nav.get_curr_frame() + + if curr_frame not in (CreateAccountBarcode, CreateAccountManual): + return + + self._id_string += key.char + logging.debug("The array is now: " + repr(str(self._id_string))) + + if self._id_string.endswith("\r"): + if self._id_vet(self._id_string) == "bad": + self._id_string = "" + if not self._swipe_error_shown: + self._swipe_error_shown = True + id_error = tkinter.Label( + self.ctx.window.canvas, text="Error, please scan again", + bg="#153246", fg="white", font=("Arial", 20), + ) + id_error.place(relx=0.5, rely=0.85, anchor="center") + id_error.after(1500, lambda: self._destroy_swipe_error(id_error)) + return + + logging.info(f"Card barcode read: {self._id_string.strip()!r}") + self.ctx.account.create_account_from_barcode(self._id_string.strip()) + self._id_string = "" + + def _destroy_swipe_error(self, id_error): + id_error.destroy() + self._swipe_error_shown = False diff --git a/src/core/__init__.py b/src/core/__init__.py deleted file mode 100644 index 32f632f..0000000 --- a/src/core/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# This is empty to ensure the directory is recognized as a package. -# You can add any necessary imports or initializations here if needed. \ No newline at end of file diff --git a/src/core/handle_check_in.py b/src/core/handle_check_in.py deleted file mode 100644 index 9d686cb..0000000 --- a/src/core/handle_check_in.py +++ /dev/null @@ -1,53 +0,0 @@ -import global_ -import logging -from tkinter import Label -from core.write_checkin import write_checkin - - -def handle_check_in(tag): - result = global_.sheets.checkin_by_uuid(tag) - status = result.get("status") - - def update_ui(): - from screens.MainPage import MainPage - from screens.NoAccNoWaiver import NoAccNoWaiver - from screens.NoAccNoWaiverSwipe import NoAccNoWaiverSwipe - from screens.AccNoWaiver import AccNoWaiver - from screens.AccNoWaiverSwipe import AccNoWaiverSwipe - from screens.UserWelcome import UserWelcome - - if status == "api_error": - logging.error("API error during check-in") - global_.traffic_light.set_red() - error_label = Label( - global_.app.canvas, - text="System error, please let staff know.", - bg="#153246", fg="white", font=("Arial", 25), - ) - error_label.place(relx=0.5, rely=0.1, anchor="center") - error_label.after(4000, error_label.destroy) - return - - if status == "no_account": - logging.info(f"User {tag} not found.") - global_.traffic_light.set_red() - global_.app.show_frame(NoAccNoWaiver) - global_.app.after(3000, lambda: global_.app.show_frame(NoAccNoWaiverSwipe)) - return - - if status == "no_waiver": - logging.info(f"User {tag} does not have waiver.") - global_.traffic_light.set_yellow() - global_.app.show_frame(AccNoWaiver) - global_.app.after(3000, lambda: global_.app.show_frame(AccNoWaiverSwipe)) - return - - logging.info(f"User found: {result['name']}") - global_.traffic_light.set_green() - global_.app.get_frame(UserWelcome).displayName(result["name"]) - write_checkin({ - "Name": result["name"], - "Student ID": result["student_id"], - }, tag) - - global_.app.after(0, update_ui) diff --git a/src/core/render_ports.py b/src/core/render_ports.py deleted file mode 100644 index 6609188..0000000 --- a/src/core/render_ports.py +++ /dev/null @@ -1,16 +0,0 @@ -import serial.tools.list_ports - -SHARED_VID = 0x1A86 -TRAFFIC_LOCATION = "1-1.1.2" - - -def get_usb_ids(): - reader_usb_id = None - traffic_usb_id = None - for p in serial.tools.list_ports.comports(): - if p.vid == SHARED_VID: - if p.location == TRAFFIC_LOCATION: - traffic_usb_id = p.device - else: - reader_usb_id = p.device - return reader_usb_id, traffic_usb_id diff --git a/src/core/write_checkin.py b/src/core/write_checkin.py deleted file mode 100644 index 720dc98..0000000 --- a/src/core/write_checkin.py +++ /dev/null @@ -1,27 +0,0 @@ -import os -import json -from datetime import datetime as dt - -LOG_BASE_PATH = "assets/logs" - -def write_checkin(curr_user, tag): - now = dt.now() - year = now.strftime("%Y") - month = now.strftime("%m") - day = now.strftime("%d") - timestamp = now.isoformat(timespec="seconds") - - log_dir = os.path.join(LOG_BASE_PATH, year, month) - os.makedirs(log_dir, exist_ok=True) - - log_path = os.path.join(log_dir, f"{day}.log") - log_entry = { - "tag": tag, - "name": curr_user.get("Name", ""), - "pid": curr_user.get("Student ID", ""), - "timestamp": timestamp, - } - - with open(log_path, "a", encoding="utf-8") as f: - json.dump(log_entry, f) - f.write("\n") \ No newline at end of file diff --git a/src/get_info_from_pid.py b/src/get_info_from_pid.py deleted file mode 100644 index 76b4924..0000000 --- a/src/get_info_from_pid.py +++ /dev/null @@ -1,69 +0,0 @@ -import logging -import time - -import requests - -from config import API_BASE_URL - - -def _req(method, url, **kwargs): - start = time.time() - resp = requests.request(method, url, **kwargs) - ms = (time.time() - start) * 1000 - logging.info(f"[CLIENT] {method.upper()} {url} -> {resp.status_code} ({ms:.0f}ms)") - return resp - - -class contact_client: - def get_student_info(self, barcode): - try: - resp = _req("GET", f"{API_BASE_URL}/students/barcode/{barcode}", timeout=5) - if not resp.ok: - return False - d = resp.json() - return [d["first_name"], d["last_name"], d["emails"], d["pid"], d["first_enr_term"], d["last_enr_term"]] - except Exception as e: - logging.error(f"Error fetching student by barcode: {e}") - return False - - def get_student_info_pid(self, pid): - try: - resp = _req("GET", f"{API_BASE_URL}/students/pid/{pid}", timeout=5) - if not resp.ok: - return False - d = resp.json() - return [d["first_name"], d["last_name"], d["emails"], d["pid"], d["first_enr_term"], d["last_enr_term"]] - except Exception as e: - logging.error(f"Error fetching student by pid: {e}") - return False - - # TODO: I assume this was probably to add in support for employee checkin, when reimplemented - # TODO: it should be started with an implementation on the api side - # # not yet tested, still need to be authorized access to employeeData API. - # def get_staff_info(self, pid): - # if self.token["expires_at"] < time.time() + 60: - # self.token = self.oauth2_client.fetch_token( - # api_url + "token", grant_type="client_credentials" - # ) - # url = api_url + "employee_data/v1/employees/" + str(pid) - # token = self.token["access_token"] - # response = self.safe_get(url, token) - # if not response.ok: - # return False - # email = response.json()["officialEmail"] - # fname = response.json()["firstName"] - # lname = response.json()["lastName"] - # return [fname, lname, [email]] - # - # def safe_get(self, url, token, retries=2): - # for _ in range(retries): - # try: - # response = requests.get( - # url, headers={"Authorization": f"Bearer {token}"}, timeout=4 - # ) - # if response.ok: - # return response - # except (requests.exceptions.Timeout, requests.exceptions.ConnectionError): - # pass - # time.sleep(0.5) # small pause before retry - # return False diff --git a/src/global_.py b/src/global_.py deleted file mode 100644 index 1f9e8c3..0000000 --- a/src/global_.py +++ /dev/null @@ -1,48 +0,0 @@ -import threading -from sheets import SheetManager -from traffic import TrafficLight - - -class _TrafficProxy: - def __init__(self, light, sheets_mgr): - self._light = light - self._sheets = sheets_mgr - - @property - def connected(self): - return self._light.ser is not None - - def _post(self, color): - threading.Thread( - target=self._sheets.set_traffic_light, - args=(color,), - daemon=True, - ).start() - - def set_red(self): - self._post("red") - - def set_green(self): - self._post("green") - - def set_yellow(self): - self._post("yellow") - - def set_off(self): - self._post("off") - - -def init(traffic_usb_id=None): - global rfid, sheets, app, traffic_light - sheets = SheetManager() - traffic_light = _TrafficProxy(TrafficLight(traffic_usb_id), sheets) - - -def setRFID(new_rfid): - global rfid - rfid = new_rfid - - -def setApp(new_app): - global app - app = new_app diff --git a/src/gui.py b/src/gui.py deleted file mode 100644 index 6b4f3e4..0000000 --- a/src/gui.py +++ /dev/null @@ -1,112 +0,0 @@ -import uuid -import tkinter as tk -from pathlib import Path -import global_ - -ASSETS_PATH = Path(__file__).parent / "assets" / "shared" - - -################################################# -# Acts as the controller and the user interface # -################################################# -class gui(tk.Tk): - def __init__(self, *args, **kwargs): - tk.Tk.__init__(self, *args, **kwargs) - - self.title("Check-In") - self.geometry("1280x720") - self.bind("", self._on_map) - - # Single shared canvas — background is always painted here, never redrawn - self.canvas = tk.Canvas( - self, - bg="#153246", - height=720, - width=1280, - bd=0, - highlightthickness=0, - ) - self.canvas.pack(fill="both", expand=True) - - # Load background images once and draw them permanently - self._bg_photos = [] - bg1 = tk.PhotoImage(file=str(ASSETS_PATH / "image_1.png")) - self._bg_photos.append(bg1) - self.canvas.create_image(640.0, 360.0, image=bg1) - - bg2 = tk.PhotoImage(file=str(ASSETS_PATH / "image_2.png")) - self._bg_photos.append(bg2) - self.canvas.create_image(639.333984375, 359.333984375, image=bg2) - - self.frames = {} - self.curr_frame = None - self.frame_uuid = uuid.uuid4().hex - - from screens.MainPage import MainPage - from screens.AccNoWaiver import AccNoWaiver - from screens.AccNoWaiverSwipe import AccNoWaiverSwipe - from screens.ManualFill import ManualFill - from screens.CheckInNoId import CheckInNoId - from screens.NoAccCheckInOnly import NoAccCheckInOnly - from screens.NoAccNoWaiver import NoAccNoWaiver - from screens.NoAccNoWaiverSwipe import NoAccNoWaiverSwipe - from screens.QRCodes import QRCodes - from screens.UserThank import UserThank - from screens.UserWelcome import UserWelcome - from screens.WaiverNoAcc import WaiverNoAcc - from screens.WaiverNoAccSwipe import WaiverNoAccSwipe - - self._timeouts = { - AccNoWaiverSwipe: 30000, - QRCodes: 30000, - NoAccNoWaiverSwipe: 30000, - } - - for F in ( - MainPage, - AccNoWaiver, - AccNoWaiverSwipe, - ManualFill, - CheckInNoId, - NoAccCheckInOnly, - NoAccNoWaiver, - NoAccNoWaiverSwipe, - QRCodes, - UserThank, - UserWelcome, - WaiverNoAcc, - WaiverNoAccSwipe, - ): - self.frames[F] = F(self.canvas, self) - - self.show_frame(MainPage) - - def _on_map(self, event): - self.unbind("") - self.attributes("-fullscreen", True) - - def timeout_fn(self, curr_uuid): - from screens.MainPage import MainPage - if curr_uuid == self.frame_uuid: - self.show_frame(MainPage) - global_.traffic_light.set_off() - - def show_frame(self, cont): - if self.curr_frame is not None: - self.frames[self.curr_frame].hide() - self.curr_frame = cont - self.frame_uuid = uuid.uuid4().hex - self.frames[cont].show() - - if cont in self._timeouts: - curr_uuid = self.frame_uuid - self.after(self._timeouts[cont], lambda: self.timeout_fn(curr_uuid)) - - def get_frame(self, cont): - return self.frames[cont] - - def get_curr_frame(self): - return self.curr_frame - - def start(self): - self.mainloop() diff --git a/src/hardware/__init__.py b/src/hardware/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/reader.py b/src/hardware/rfid_reader.py similarity index 93% rename from src/reader.py rename to src/hardware/rfid_reader.py index 7b83ea5..06bfa6b 100644 --- a/src/reader.py +++ b/src/hardware/rfid_reader.py @@ -40,7 +40,7 @@ def reconnect(self): self._pn532 = None return False - def getSerInWaiting(self): + def get_ser_in_waiting(self): try: uid = self._pn532.read_passive_target(timeout=0.1) except Exception as e: @@ -53,16 +53,16 @@ def getSerInWaiting(self): self._pending_tag = None return 0 - def grabRFID(self): + def grab_rfid(self): tag = self._pending_tag self._pending_tag = None logging.info("Parsed tag: " + str(tag)) return str(tag) - def checkRFID(self, tag): + def check_rfid(self, tag): if not tag or len(tag) != expected_characters: return "Tag was not the expected number of chars" return "good" - def canScanAgain(self, lastTime): + def can_scan_again(self, lastTime): return time.time() - lastTime > 3 diff --git a/src/traffic.py b/src/hardware/traffic_light.py similarity index 92% rename from src/traffic.py rename to src/hardware/traffic_light.py index ace092a..6f3ce61 100644 --- a/src/traffic.py +++ b/src/hardware/traffic_light.py @@ -11,6 +11,10 @@ def __init__(self, addr=None, baud=115200): self.ser = serial.Serial(addr, baud) self.ser.reset_input_buffer() + @property + def connected(self) -> bool: + return self.ser is not None + def set_off(self): if self.ser: self.ser.write(b"off\n") diff --git a/src/hardware/usb_ports.py b/src/hardware/usb_ports.py new file mode 100644 index 0000000..2ad7cca --- /dev/null +++ b/src/hardware/usb_ports.py @@ -0,0 +1,24 @@ +from dataclasses import dataclass + +import serial.tools.list_ports + +SHARED_VID = 0x1A86 +TRAFFIC_LOCATION = "1-1.1.2" + + +@dataclass +class UsbIds: + reader: str | None + traffic_light: str | None + + +def get_usb_ids() -> UsbIds: + reader = None + traffic_light = None + for p in serial.tools.list_ports.comports(): + if p.vid == SHARED_VID: + if p.location == TRAFFIC_LOCATION: + traffic_light = p.device + else: + reader = p.device + return UsbIds(reader, traffic_light) diff --git a/src/main.py b/src/main.py index c8e6c85..70f0fc4 100644 --- a/src/main.py +++ b/src/main.py @@ -1,168 +1,60 @@ -from tkinter import Label -from gui import gui -from reader import * -from swipe import swipe -from sheets import * -from threading import Thread -from screens.MainPage import MainPage -from screens.ManualFill import ManualFill -from screens.CheckInNoId import CheckInNoId -from utils import utils -from core.handle_check_in import handle_check_in -from core.render_ports import get_usb_ids -import global_ -import socket +from window import CheckInWindow +from controllers.navigation_controller import NavigationController +from controllers.swipe_controller import SwipeController +from controllers.check_in_controller import CheckInController +from controllers.account_controller import AccountController +from controllers.rfid_reader_controller import RfidReaderController +from hardware.rfid_reader import Reader +from screens.create_account_manual import CreateAccountManual +from screens.check_in_manual import CheckInManual +from hardware.usb_ports import get_usb_ids +from app_context import AppContext +from api.client import check_api_health import logging import argparse from sys import stdout -from sheets import check_api_health -def is_connected(host="8.8.8.8", port=53, timeout=3): - """ - Host: 8.8.8.8 (google-public-dns-a.google.com) - OpenPort: 53/tcp - Service: domain (DNS/TCP) - """ - try: - socket.setdefaulttimeout(timeout) - socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((host, port)) - return True - except socket.error as ex: - return False -############################################################## -# This acts as the main loop of the program, ran in a thread # -############################################################## +def clear_and_return(ctx: AppContext): + ctx.nav.back_to_main() + ctx.nav.get_frame(CreateAccountManual).clear_entries() + ctx.nav.get_frame(CheckInManual).clear_entries() -no_wifi_shown = False - -def myLoop(app, reader): - global no_wifi_shown, no_wifi - logging.info("Now reading ID cards") - last_tag = 0 - last_time = 0 - scanner_error = False - while True: - if scanner_error: - time.sleep(0.1) - if reader.reconnect(): - logging.info("Card reader reconnected") - scanner_error = False - continue - - try: - in_waiting = reader.getSerInWaiting() - except OSError as e: - if not scanner_error: - logging.error("Card reader disconnected, disabling until reconnection: %s", e) - scanner_error = True - continue - - if in_waiting >= 14: - if not is_connected(): - logging.info("ERROR wifi is not connected") - if not no_wifi_shown: - no_wifi_shown = True - no_wifi = Label( - app.canvas, - text="ERROR! Connection cannot be established, please let staff know.", - bg="#153246", fg="white", font=("Arial", 25), - ) - no_wifi.place(relx=0.5, rely=0.1, anchor="center") - no_wifi.after(4000, lambda: destroyNoWifiError(no_wifi)) - continue - - app.get_frame(ManualFill).clearEntries() - tag = reader.grabRFID() - - if " " in tag: - continue - - if tag == last_tag and not reader.canScanAgain(last_time): - logging.debug("Suppressing repeat scan") - continue - - s_reason = reader.checkRFID(tag) - - if s_reason != "good": - logging.debug(s_reason) - continue - else: - logging.debug("RFID Check Succeeded") - - global_.setRFID(tag) - handle_check_in(tag) - - last_tag = tag - last_time = time.time() - -def trafficLightPoller(): - last_color = None - light = global_.traffic_light._light - while True: - time.sleep(0.1) - color = global_.sheets.get_traffic_light() - if color != last_color: - last_color = color - if color == "red": - light.set_red() - elif color == "green": - light.set_green() - elif color == "yellow": - light.set_yellow() - else: - light.set_off() - - -def destroyNoWifiError(no_wifi): - global no_wifi_shown - no_wifi.destroy() - no_wifi_shown = False - -def clearAndReturn(): - global_.traffic_light.set_off() - global_.app.show_frame(MainPage) - global_.app.get_frame(ManualFill).clearEntries() - global_.app.get_frame(CheckInNoId).clearEntries() if __name__ == "__main__": parser = argparse.ArgumentParser( description="Makerspace Check-in System", formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) - - parser.add_argument( - "-v", - "--verbose", - action="store_true", - help="Increase verbosity (print debug info)", - ) - + parser.add_argument("-v", "--verbose", action="store_true", help="Increase verbosity (print debug info)") + parser.add_argument("-d", "--dev", action="store_true", help="Enable dev mode with on-screen navigation overlay") args = parser.parse_args() - config = vars(args) - if config["verbose"]: + if args.verbose: logging.basicConfig(level=logging.DEBUG, stream=stdout) else: logging.basicConfig(level=logging.INFO) - reader_usb_id, traffic_usb_id = get_usb_ids() - check_api_health() - global_.init(traffic_usb_id) - app = gui() - global_.setApp(app) - global_.traffic_light.set_off() + import os + dev_mode = args.dev or os.environ.get("DEV_MODE") == "1" - sw = swipe() - reader = Reader(reader_usb_id) - util = utils() - thread = Thread(target=myLoop, args=(app, reader)) - logging.info("Starting thread") - thread.start() - if global_.traffic_light.connected: - poller = Thread(target=trafficLightPoller, daemon=True) - poller.start() - app.bind("", lambda i: sw.keyboardPress(i)) - app.bind("", lambda i: clearAndReturn()) + usb = get_usb_ids() + check_api_health() + ctx = AppContext.create(usb.traffic_light) + window = CheckInWindow() + nav = NavigationController(window, ctx, dev_mode=dev_mode) + ctx.window = window + ctx.nav = nav + ctx.check_in = CheckInController(ctx) + ctx.account = AccountController(ctx) + ctx.traffic_light.request_off() + + sw = SwipeController(ctx) + reader = Reader(usb.reader) + card_reader = RfidReaderController(ctx) + card_reader.start(reader) + + window.bind("", lambda i: sw.keyboard_press(i)) + window.bind("", lambda i: clear_and_return(ctx)) logging.info("Made it to app start") - app.start() \ No newline at end of file + window.start() diff --git a/src/main_checkin_only.py b/src/main_checkin_only.py deleted file mode 100644 index 3456ce7..0000000 --- a/src/main_checkin_only.py +++ /dev/null @@ -1,139 +0,0 @@ -from datetime import datetime as dt -from core.handle_check_in import handle_check_in -from core.render_ports import get_usb_ids -from gui import * -from reader import * -import json -from sheets import * -from threading import Thread -from UserWelcome import * -from ManualFill import * -from CheckInNoId import * -from swipe import * -from tkinter import * -import global_ -import socket -import logging -import argparse -from sheets import check_api_health - - -def is_connected(host="8.8.8.8", port=53, timeout=3): - """ - Host: 8.8.8.8 (google-public-dns-a.google.com) - OpenPort: 53/tcp - Service: domain (DNS/TCP) - """ - try: - socket.setdefaulttimeout(timeout) - socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((host, port)) - return True - except socket.error as ex: - return False - - -############################################################## -# This acts as the main loop of the program, ran in a thread # -############################################################## - -no_wifi_shown = False - - -def myLoop(app, reader): - global no_wifi_shown, no_wifi - logging.info("Now reading ID cards") - last_tag = 0 - last_time = 0 - while True: - time.sleep(0.1) - in_waiting = reader.getSerInWaiting() - tag = 0 - - if in_waiting >= 14: - if not is_connected(): - logging.info("ERROR wifi is not connected") - if not no_wifi_shown: - no_wifi_shown = True - no_wifi = Label( - app.get_frame(MainPage), - text="ERROR! Connection cannot be established, please let staff know.", - font=("Arial", 25), - ) - no_wifi.pack(pady=40) - no_wifi.after(4000, lambda: destroyNoWifiError(no_wifi)) - continue - - tag_read_start = perf_counter() - tag = reader.grabRFID() - tag_read_end = perf_counter() - - if " " in tag: - continue - - if tag == last_tag and not reader.canScanAgain(last_time): - logging.debug("Suppressing repeat scan") - continue - - s_reason = reader.checkRFID(tag) - - if s_reason != "good": - logging.debug(s_reason) - continue - else: - logging.debug("RFID Check Succeeded") - - global_.setRFID(tag) - handle_check_in(tag, contact, util) - - last_tag = tag - last_time = time.time() - - reader.readSerial() - -def destroyNoWifiError(no_wifi): - global no_wifi_shown - no_wifi.destroy() - no_wifi_shown = False - - -def clearAndReturn(): - global_.app.show_frame(MainPage) - global_.app.get_frame(ManualFill).clearEntries() - global_.app.get_frame(CheckInNoId).clearEntries() - - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - description="Makerspace Check-in System", - formatter_class=argparse.ArgumentDefaultsHelpFormatter, - ) - - parser.add_argument( - "-v", - "--verbose", - action="store_true", - help="Increase verbosity (print debug info)", - ) - - args = parser.parse_args() - config = vars(args) - - if config["verbose"]: - logging.basicConfig(level=logging.DEBUG) - else: - logging.basicConfig(level=logging.INFO) - - reader_usb_id, traffic_usb_id = get_usb_ids() - check_api_health() - global_.init(traffic_usb_id) - app = gui() - global_.setApp(app) - - reader = Reader() - util = utils() - thread = Thread(target=myLoop, args=(app, reader)) - logging.info("Starting thread") - thread.start() - app.bind("", lambda i: clearAndReturn()) - logging.info("Made it to app start") - app.start() diff --git a/src/run b/src/run deleted file mode 100755 index ab07bed..0000000 --- a/src/run +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -set -a -source "$(dirname "$0")/../.env" -set +a - -output_file="log.txt" - -echo "" >> "$output_file" - -date "+%Y-%m-%d %H:%M:%S" >> "$output_file" - -python main.py 2>&1 | tee -a log.txt \ No newline at end of file diff --git a/src/run_verbose b/src/run_verbose deleted file mode 100755 index e94a274..0000000 --- a/src/run_verbose +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -set -a -source "$(dirname "$0")/../.env" -set +a - -output_file="log.txt" - -echo "" >> "$output_file" - -date "+%Y-%m-%d %H:%M:%S" >> "$output_file" - -python main.py -v 2>&1 | tee -a log.txt \ No newline at end of file diff --git a/src/screens/AccNoWaiver.py b/src/screens/AccNoWaiver.py deleted file mode 100644 index 899da8d..0000000 --- a/src/screens/AccNoWaiver.py +++ /dev/null @@ -1,10 +0,0 @@ -from .screen import Screen - - -class AccNoWaiver(Screen): - def _build(self, controller): - self._text( - 169.0, 258.0, anchor="nw", - text="Looks like you haven't signed\n the waiver, let's solve that", - fill="#F5F0E6", font=("Montserrat", 64 * -1), - ) diff --git a/src/screens/AccNoWaiverSwipe.py b/src/screens/AccNoWaiverSwipe.py deleted file mode 100644 index ea877c3..0000000 --- a/src/screens/AccNoWaiverSwipe.py +++ /dev/null @@ -1,54 +0,0 @@ -from pathlib import Path -from tkinter import Button -from .screen import Screen -import global_ - -ASSETS_PATH = Path(__file__).parent.parent / "assets" / "acc_no_waiver_swipe_assets" - - -class AccNoWaiverSwipe(Screen): - def _build(self, controller): - img2 = self._photo(ASSETS_PATH / "image_2.png") - self._image(1042.0, 359.0, image=img2) - - img3 = self._photo(ASSETS_PATH / "image_3.png") - self._image(408.0, 76.0, image=img3) - - img4 = self._photo(ASSETS_PATH / "image_4.png") - self._image(408.0, 429.0, image=img4) - - img5 = self._photo(ASSETS_PATH / "image_5.png") - self._image(395.0, 70.0, image=img5) - - img6 = self._photo(ASSETS_PATH / "image_6.png") - self._image(750.0, 70.0, image=img6) - - img7 = self._photo(ASSETS_PATH / "image_7.png") - self._image(1042.0, 328.0, image=img7) - - self._text( - 37.0, 45.0, anchor="nw", - text="Account Status:", fill="#F5F0E6", font=("Montserrat", 40 * -1), - ) - self._text( - 430.0, 45.0, anchor="nw", - text="Waiver Status:", fill="#F5F0E6", font=("Montserrat", 40 * -1), - ) - self._text( - 45.0, 270.0, anchor="nw", - text="Please scan the QR code\non the right and sign our \n waiver", - fill="#F5F0E6", font=("Montserrat", 48 * -1), - ) - - btn_img = self._photo(ASSETS_PATH / "button_1.png") - btn = Button( - self.canvas, image=btn_img, - borderwidth=0, highlightthickness=0, - command=self._back_to_main, relief="flat", - ) - self._window(875.0, 581.0, btn, width=344, height=71) - - def _back_to_main(self): - from .MainPage import MainPage - global_.traffic_light.set_off() - global_.app.show_frame(MainPage) diff --git a/src/screens/CheckInNoId.py b/src/screens/CheckInNoId.py deleted file mode 100644 index 1cd5d26..0000000 --- a/src/screens/CheckInNoId.py +++ /dev/null @@ -1,101 +0,0 @@ -from pathlib import Path -from tkinter import Button, Entry, StringVar, END -from .screen import Screen -import global_ -import logging - -ASSETS_PATH = Path(__file__).parent.parent / "assets" / "check_in_no_id_assets" - - -######################################################## -# This is the frame where users will manually check in # -######################################################## - -class CheckInNoId(Screen): - def _build(self, controller): - from .NoAccCheckInOnly import NoAccCheckInOnly - from .NoAccNoWaiverSwipe import NoAccNoWaiverSwipe - from .UserWelcome import UserWelcome - from .AccNoWaiver import AccNoWaiver - - self.loading_text_id = None - self.pid = StringVar() - - img2 = self._photo(ASSETS_PATH / "image_2.png") - self._image(640.0, 360.0, image=img2) - - img3 = self._photo(ASSETS_PATH / "image_3.png") - self._image(640.0, 424.0, image=img3) - - self._text( - 212.0, 120.0, anchor="nw", - text="If you have already made an\naccount, scan your UCSD barcode\nor enter your PID manually", - fill="#F5F0E6", font=("Montserrat", 48 * -1), justify="center", - ) - self._text( - 605.0, 480.0, anchor="nw", - text="PID", fill="#F5F0E6", font=("Montserrat", 24 * -1), - ) - - btn_img = self._photo(ASSETS_PATH / "button_1.png") - btn = Button( - self.canvas, image=btn_img, - borderwidth=0, highlightthickness=0, - command=lambda: self._call_check_in(controller), relief="flat", - ) - self._window(465.0, 598.0, btn, width=349, height=71) - - self.pid_entry = Entry(self.canvas, textvariable=self.pid, width=40, font=52) - self._window(420.0, 412.0, self.pid_entry) - - def displayLoading(self): - if self.loading_text_id is None: - self.loading_text_id = self.canvas.create_text( - 420.0, 545.0, anchor="nw", - text="PLEASE WAIT: LOADING...", - fill="#FF0000", font=("Montserrat", 36 * -1, "bold"), justify="center", - ) - - def clearEntries(self): - self.pid_entry.delete(0, END) - - def updateEntries(self, pid): - self.pid_entry.insert(0, pid) - - def _call_check_in(self, controller): - from .NoAccCheckInOnly import NoAccCheckInOnly - from .NoAccNoWaiverSwipe import NoAccNoWaiverSwipe - from .UserWelcome import UserWelcome - from .AccNoWaiver import AccNoWaiver - from .MainPage import MainPage - - pid = self.pid_entry.get() - if not pid: - return - - self.displayLoading() - self.canvas.update_idletasks() - self.clearEntries() - - result = global_.sheets.checkin_by_pid(pid) - status = result.get("status") - - if self.loading_text_id is not None: - self.canvas.delete(self.loading_text_id) - self.loading_text_id = None - - if status == "no_account": - logging.info("Manual check-in: user account not found") - controller.show_frame(NoAccCheckInOnly) - controller.after(5000, lambda: controller.show_frame(MainPage)) - return - - if status == "no_waiver": - logging.info(f"Manual check-in: no waiver for {result.get('name', pid)}") - controller.show_frame(AccNoWaiver) - controller.after(3000, lambda: controller.show_frame(NoAccNoWaiverSwipe)) - return - - logging.info(f"Manual check-in for {result['name']}") - global_.traffic_light.set_green() - global_.app.get_frame(UserWelcome).displayName(result["name"]) diff --git a/src/screens/ManualFill.py b/src/screens/ManualFill.py deleted file mode 100644 index 44e9f54..0000000 --- a/src/screens/ManualFill.py +++ /dev/null @@ -1,122 +0,0 @@ -from pathlib import Path -from tkinter import Button, Entry, StringVar, END -from .screen import Screen -from utils import utils -import logging -import timeit - -ASSETS_PATH = Path(__file__).parent.parent / "assets" / "manual_fill_assets" - - -####################################################### -# This is the frame where users will type information # -####################################################### - -class ManualFill(Screen): - def _build(self, controller): - self.first_name = StringVar() - self.last_name = StringVar() - self.email = StringVar() - self.pid = StringVar() - - img2 = self._photo(ASSETS_PATH / "image_2.png") - self._image(640.0, 76.0, image=img2) - - img3 = self._photo(ASSETS_PATH / "image_3.png") - self._image(640.0, 430.0, image=img3) - - img4 = self._photo(ASSETS_PATH / "image_4.png") - self._image(605.0, 77.0, image=img4) - - img5 = self._photo(ASSETS_PATH / "image_5.png") - self._image(1010.0, 77.0, image=img5) - - img6 = self._photo(ASSETS_PATH / "image_6.png") - self._image(640.0, 542.0, image=img6) - - img7 = self._photo(ASSETS_PATH / "image_7.png") - self._image(640.0, 440.0, image=img7) - - img8 = self._photo(ASSETS_PATH / "image_8.png") - self._image(640.0, 339.0, image=img8) - - img9 = self._photo(ASSETS_PATH / "image_9.png") - self._image(640.0, 239.0, image=img9) - - self._text( - 250.0, 45.0, anchor="nw", - text="Account Status:", fill="#F5F0E6", font=("Montserrat", 40 * -1), - ) - self._text( - 670.0, 45.0, anchor="nw", - text="Waiver Status:", fill="#F5F0E6", font=("Montserrat", 40 * -1), - ) - self._text( - 565.0, 177.0, anchor="nw", - text="First Name", fill="#F5F0E6", font=("Montserrat", 24 * -1), - ) - self._text( - 565.0, 278.0, anchor="nw", - text="Last Name", fill="#F5F0E6", font=("Montserrat", 24 * -1), - ) - self._text( - 595.0, 379.0, anchor="nw", - text="Email", fill="#F5F0E6", font=("Montserrat", 24 * -1), - ) - self._text( - 605.0, 480.0, anchor="nw", - text="PID", fill="#F5F0E6", font=("Montserrat", 24 * -1), - ) - - btn_img = self._photo(ASSETS_PATH / "button_1.png") - btn = Button( - self.canvas, image=btn_img, - borderwidth=0, highlightthickness=0, - command=self._call_account_creation, relief="flat", - ) - self._window(465.0, 598.0, btn, width=349, height=71) - - self.first_name_entry = Entry(self.canvas, textvariable=self.first_name, width=40, font=52) - self._window(420.0, 227.0, self.first_name_entry) - - self.last_name_entry = Entry(self.canvas, textvariable=self.last_name, width=40, font=52) - self._window(420.0, 327.0, self.last_name_entry) - - self.email_entry = Entry(self.canvas, textvariable=self.email, width=40, font=52) - self._window(420.0, 428.0, self.email_entry) - - self.pid_entry = Entry(self.canvas, textvariable=self.pid, width=40, font=52) - self._window(420.0, 530.0, self.pid_entry) - - def getEntries(self): - return [ - self.first_name.get(), - self.last_name.get(), - self.email.get(), - self.pid.get(), - ] - - def clearEntries(self): - self.first_name_entry.delete(0, END) - self.last_name_entry.delete(0, END) - self.email_entry.delete(0, END) - self.pid_entry.delete(0, END) - - def updateEntries(self, fname, lname, email, pid): - self.first_name_entry.insert(0, fname) - self.last_name_entry.insert(0, lname) - self.email_entry.insert(0, email) - self.pid_entry.insert(0, pid) - - def _call_account_creation(self): - util = utils() - data = self.getEntries() - self.clearEntries() - try: - delay = timeit.timeit( - lambda: util.createAccount(data[0], data[1], data[2], data[3], ManualFill), - number=1, - ) - logging.debug(f"Time to create account: {delay}") - except Exception: - logging.warning("Error occurred trying to create a user account", exc_info=True) diff --git a/src/screens/NoAccCheckInOnly.py b/src/screens/NoAccCheckInOnly.py deleted file mode 100644 index e9dde38..0000000 --- a/src/screens/NoAccCheckInOnly.py +++ /dev/null @@ -1,10 +0,0 @@ -from .screen import Screen - - -class NoAccCheckInOnly(Screen): - def _build(self, controller): - self._text( - 160.0, 180.0, anchor="nw", - text="Looks like you don't have an\n account, please scan your ID\nat the main desk", - fill="#F5F0E6", font=("Montserrat", 64 * -1), - ) diff --git a/src/screens/NoAccNoWaiver.py b/src/screens/NoAccNoWaiver.py deleted file mode 100644 index 5377ebe..0000000 --- a/src/screens/NoAccNoWaiver.py +++ /dev/null @@ -1,10 +0,0 @@ -from .screen import Screen - - -class NoAccNoWaiver(Screen): - def _build(self, controller): - self._text( - 80.0, 180.0, anchor="nw", - text="Looks like your card isn't registered, \n let's set up your account.", - fill="#F5F0E6", font=("Montserrat", 64 * -1), - ) diff --git a/src/screens/NoAccNoWaiverSwipe.py b/src/screens/NoAccNoWaiverSwipe.py deleted file mode 100644 index 57c86c2..0000000 --- a/src/screens/NoAccNoWaiverSwipe.py +++ /dev/null @@ -1,48 +0,0 @@ -from pathlib import Path -from tkinter import Button -from .screen import Screen -import global_ - -ASSETS_PATH = Path(__file__).parent.parent / "assets" / "no_acc_no_waiver_swipe_assets" - - -class NoAccNoWaiverSwipe(Screen): - def _build(self, controller): - img2 = self._photo(ASSETS_PATH / "image_2.png") - self._image(640.0, 76.0, image=img2) - - img3 = self._photo(ASSETS_PATH / "image_3.png") - self._image(640.0, 430.0, image=img3) - - img4 = self._photo(ASSETS_PATH / "image_4.png") - self._image(576.0, 65.0, image=img4) - - img5 = self._photo(ASSETS_PATH / "image_5.png") - self._image(1030.0, 65.0, image=img5) - - self._text( - 303.0, 350.0, anchor="nw", - text="Please scan your ID barcode", - fill="#F5F0E6", font=("Montserrat", 48 * -1), - ) - self._text( - 215.0, 45.0, anchor="nw", - text="Account Status:", fill="#F5F0E6", font=("Montserrat", 40 * -1), - ) - self._text( - 690.0, 45.0, anchor="nw", - text="Waiver Status:", fill="#F5F0E6", font=("Montserrat", 40 * -1), - ) - - btn_img = self._photo(ASSETS_PATH / "button_1.png") - btn = Button( - self.canvas, image=btn_img, - borderwidth=0, highlightthickness=0, - command=lambda: self._go_to_manual_fill(controller), relief="flat", - ) - self._window(465.0, 554.0, btn, width=349, height=71) - - def _go_to_manual_fill(self, controller): - from .ManualFill import ManualFill - global_.app.get_frame(ManualFill).clearEntries() - controller.show_frame(ManualFill) diff --git a/src/screens/QRCodes.py b/src/screens/QRCodes.py deleted file mode 100644 index e05205d..0000000 --- a/src/screens/QRCodes.py +++ /dev/null @@ -1,36 +0,0 @@ -from pathlib import Path -from tkinter import Button -from .screen import Screen - -ASSETS_PATH = Path(__file__).parent.parent / "assets" / "qr_codes_assets" - - -class QRCodes(Screen): - def _build(self, controller): - from .MainPage import MainPage - - img3 = self._photo(ASSETS_PATH / "image_3.png") - self._image(88.0, 90.0, image=img3) - - img4 = self._photo(ASSETS_PATH / "image_4.png") - self._image(421.0, 360.0, image=img4) - - img5 = self._photo(ASSETS_PATH / "image_5.png") - self._image(859.0, 360.0, image=img5) - - self._text( - 335.0, 551.0, anchor="nw", - text="Website", fill="#F5F0E6", font=("Montserrat", 40 * -1), - ) - self._text( - 788.0, 557.0, anchor="nw", - text="Waiver", fill="#F5F0E6", font=("Montserrat", 40 * -1), - ) - - btn_img = self._photo(ASSETS_PATH / "image_6.png") - btn = Button( - self.canvas, image=btn_img, bg="#153246", - command=lambda: controller.show_frame(MainPage), - relief="flat", - ) - self._window(53.0, 55.0, btn) diff --git a/src/screens/UserThank.py b/src/screens/UserThank.py deleted file mode 100644 index e5ff541..0000000 --- a/src/screens/UserThank.py +++ /dev/null @@ -1,46 +0,0 @@ -from .screen import Screen -import global_ - - -class UserThank(Screen): - def _build(self, controller): - self._text( - 99.33203125, 259.33203125, anchor="nw", - text="Thank you for registering", - fill="#F5F0E6", font=("Montserrat", 45 * -1), - ) - self._text( - 429.0, 550.0, anchor="nw", - text="UCSD Makerspace", - fill="#F5F0E6", font=("Montserrat", 45 * -1), - ) - - def hide(self): - super().hide() - self.canvas.delete("thank") - - def displayName(self, name, nextPage): - from .MainPage import MainPage - global_.app.show_frame(UserThank) - - if nextPage == MainPage: - global_.traffic_light.set_green() - else: - global_.traffic_light.set_yellow() - - self.canvas.create_text( - 99.0, 323.0, anchor="nw", - text=name, - fill="#F5F0E6", - font=("Montserrat", 73 * -1), - tag="thank", - ) - - self.canvas.after(4500, lambda: self.canvas.delete("thank")) - global_.app.after(4000, lambda: self._go_to_next(nextPage)) - - def _go_to_next(self, nextPage): - from .MainPage import MainPage - global_.app.show_frame(nextPage) - if nextPage == MainPage: - global_.traffic_light.set_off() diff --git a/src/screens/WaiverNoAcc.py b/src/screens/WaiverNoAcc.py deleted file mode 100644 index a1029a6..0000000 --- a/src/screens/WaiverNoAcc.py +++ /dev/null @@ -1,10 +0,0 @@ -from .screen import Screen - - -class WaiverNoAcc(Screen): - def _build(self, controller): - self._text( - 191.0, 258.0, anchor="nw", - text="Looks like you don't have an\n account, let's solve that", - fill="#F5F0E6", font=("Montserrat", 64 * -1), - ) diff --git a/src/screens/WaiverNoAccSwipe.py b/src/screens/WaiverNoAccSwipe.py deleted file mode 100644 index 8c841c3..0000000 --- a/src/screens/WaiverNoAccSwipe.py +++ /dev/null @@ -1,48 +0,0 @@ -from pathlib import Path -from tkinter import Button -from .screen import Screen -import global_ - -ASSETS_PATH = Path(__file__).parent.parent / "assets" / "waiver_no_acc_swipe_assets" - - -class WaiverNoAccSwipe(Screen): - def _build(self, controller): - img2 = self._photo(ASSETS_PATH / "image_2.png") - self._image(640.0, 76.0, image=img2) - - img3 = self._photo(ASSETS_PATH / "image_3.png") - self._image(640.0, 430.0, image=img3) - - img4 = self._photo(ASSETS_PATH / "image_4.png") - self._image(576.0, 65.0, image=img4) - - img5 = self._photo(ASSETS_PATH / "image_5.png") - self._image(1030.0, 65.0, image=img5) - - self._text( - 420.0, 350.0, anchor="nw", - text="Please scan your ID barcode", - fill="#F5F0E6", font=("Montserrat", 48 * -1), - ) - self._text( - 215.0, 45.0, anchor="nw", - text="Account Status:", fill="#F5F0E6", font=("Montserrat", 40 * -1), - ) - self._text( - 690.0, 45.0, anchor="nw", - text="Waiver Status:", fill="#F5F0E6", font=("Montserrat", 40 * -1), - ) - - btn_img = self._photo(ASSETS_PATH / "button_1.png") - btn = Button( - self.canvas, image=btn_img, - borderwidth=0, highlightthickness=0, - command=lambda: self._go_to_manual_fill(controller), relief="flat", - ) - self._window(465.0, 554.0, btn, width=349, height=71) - - def _go_to_manual_fill(self, controller): - from .ManualFill import ManualFill - global_.app.get_frame(ManualFill).clearEntries() - controller.show_frame(ManualFill) diff --git a/src/screens/screen.py b/src/screens/base.py similarity index 79% rename from src/screens/screen.py rename to src/screens/base.py index 054629c..73a2b21 100644 --- a/src/screens/screen.py +++ b/src/screens/base.py @@ -11,10 +11,6 @@ def __init__(self, canvas, controller): self._build(controller) self.hide() - # ------------------------------------------------------------------ - # Helpers for subclasses - # ------------------------------------------------------------------ - def _photo(self, path): img = PhotoImage(file=str(path)) self._photos.append(img) @@ -30,8 +26,13 @@ def _text(self, x, y, **kwargs): self._items.append(item) return item + def _canvas_entry(self, x, y, w, h, font, fg="#F5F0E6"): + from .components.canvas_entry import CanvasEntry + entry = CanvasEntry(self.canvas, x, y, w, h, font, fg) + self._items.extend(entry.item_ids) + return entry + def _window(self, x, y, widget, width=None, height=None): - """Embed a tk widget into the canvas. x, y = top-left corner.""" kw = dict(anchor="nw", window=widget) if width is not None: kw["width"] = width @@ -41,10 +42,6 @@ def _window(self, x, y, widget, width=None, height=None): self._windows.append(item) return item - # ------------------------------------------------------------------ - # Visibility - # ------------------------------------------------------------------ - def show(self): for item in self._items: self.canvas.itemconfigure(item, state="normal") diff --git a/src/screens/check_in_manual.py b/src/screens/check_in_manual.py new file mode 100644 index 0000000..e0df8d6 --- /dev/null +++ b/src/screens/check_in_manual.py @@ -0,0 +1,81 @@ +from pathlib import Path +from tkinter import Button, END +from .base import Screen +from .components.canvas_entry import CanvasEntry + +ASSETS_PATH = Path(__file__).parent.parent / "assets" / "check_in_manual" +SHARED_PATH = Path(__file__).parent.parent / "assets" / "shared" + + +class CheckInManual(Screen): + def _build(self, controller): + self.loading_text_id = None + + logo = self._photo(SHARED_PATH / "button_generic.png") + self._image(88.0, 90.0, image=logo) + + home_img = self._photo(SHARED_PATH / "icon_home.png") + home_btn = Button( + self.canvas, image=home_img, bg="#153246", + command=lambda: controller.back_to_main(), + relief="flat", highlightthickness=0, bd=0, + ) + self._window(53.0, 55.0, home_btn) + + field_img = self._photo(SHARED_PATH / "field.png") + self._image(640.0, 424.0, image=field_img) + + self._text( + 640.0, 206.0, anchor="center", + text="If you have already made an\naccount, scan your UCSD barcode\nor enter your PID manually", + fill="#F5F0E6", font=("Montserrat", 48 * -1), justify="center", + ) + self._text( + 640.0, 492.0, anchor="center", + text="PID", fill="#F5F0E6", font=("Montserrat", 24 * -1), + ) + + btn_img = self._photo(ASSETS_PATH / "button_check_in.png") + btn = Button( + self.canvas, image=btn_img, + borderwidth=0, highlightthickness=0, + command=lambda: self._call_check_in(controller), relief="flat", + ) + self._window(465.0, 598.0, btn, width=349, height=71) + + self.pid_entry = self._canvas_entry( + 640.0, 424.0, w=800, h=44, font=("Montserrat", 20), + ) + + def hide(self): + CanvasEntry.blur_all() + super().hide() + + def display_loading(self): + if self.loading_text_id is None: + self.loading_text_id = self.canvas.create_text( + 420.0, 545.0, anchor="nw", + text="PLEASE WAIT: LOADING...", + fill="#FF0000", font=("Montserrat", 36 * -1, "bold"), justify="center", + ) + + def clear_entries(self): + self.pid_entry.delete(0, END) + + def update_entries(self, pid): + self.pid_entry.insert(0, pid) + + def _call_check_in(self, controller): + pid = self.pid_entry.get() + if not pid: + return + + self.display_loading() + self.canvas.update_idletasks() + self.clear_entries() + + self.controller.ctx.check_in.handle_by_pid(pid) + + if self.loading_text_id is not None: + self.canvas.delete(self.loading_text_id) + self.loading_text_id = None diff --git a/src/screens/MainPage.py b/src/screens/check_in_rfid.py similarity index 63% rename from src/screens/MainPage.py rename to src/screens/check_in_rfid.py index c05e873..cdd72ed 100644 --- a/src/screens/MainPage.py +++ b/src/screens/check_in_rfid.py @@ -1,21 +1,20 @@ from pathlib import Path from tkinter import Button -from .screen import Screen -import global_ +from .base import Screen +from .qr_codes import QRCodes -ASSETS_PATH = Path(__file__).parent.parent / "assets" / "main_page_assets" +ASSETS_PATH = Path(__file__).parent.parent / "assets" / "check_in_rfid" +SHARED_PATH = Path(__file__).parent.parent / "assets" / "shared" -class MainPage(Screen): +class CheckInRFID(Screen): def _build(self, controller): - from .QRCodes import QRCodes - from .CheckInNoId import CheckInNoId - logo = self._photo(ASSETS_PATH / "image_3.png") + logo = self._photo(SHARED_PATH / "button_generic.png") self._image(88.0, 90.0, image=logo) self._text( - 336.0, 602.0, anchor="nw", + 640.0, 618.0, anchor="center", text="Please tap ID on the black box to start", fill="#F5F0E6", font=("Montserrat", 32 * -1), ) @@ -30,7 +29,7 @@ def _build(self, controller): fill="#F5F0E6", font=("Montserrat", 73 * -1), ) - btn1_img = self._photo(ASSETS_PATH / "image_4.png") + btn1_img = self._photo(ASSETS_PATH / "icon_check_in.png") btn1 = Button( self.canvas, image=btn1_img, bg="#153246", command=lambda: controller.show_frame(QRCodes), @@ -41,14 +40,8 @@ def _build(self, controller): btn2 = Button( self.canvas, image=logo, text="No\nID", compound="center", bg="#153246", fg="white", - command=lambda: self._go_to_no_id(controller), + command=lambda: controller.go_to_no_id(), relief="flat", highlightthickness=0, bd=0, font=("Montserrat", 36 * -1), ) self._window(1130.0, 40.0, btn2) - - def _go_to_no_id(self, controller): - from .CheckInNoId import CheckInNoId - no_id = global_.app.get_frame(CheckInNoId) - no_id.clearEntries() - controller.show_frame(CheckInNoId) diff --git a/src/screens/components/__init__.py b/src/screens/components/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/screens/components/canvas_entry.py b/src/screens/components/canvas_entry.py new file mode 100644 index 0000000..e46d0d7 --- /dev/null +++ b/src/screens/components/canvas_entry.py @@ -0,0 +1,73 @@ +import tkinter as tk + +_focused = None + + +class CanvasEntry: + def __init__(self, canvas, x, y, w, h, font, fg="#F5F0E6"): + self.canvas = canvas + self._x = x + self._y = y + + canvas.configure(insertbackground=fg, insertontime=600, insertofftime=400) + + self._hit_id = canvas.create_rectangle( + x - w / 2, y - h / 2, x + w / 2, y + h / 2, + fill="", outline="", state="hidden", + ) + self._text_id = canvas.create_text( + x, y, text="", fill=fg, font=font, + anchor="center", state="hidden", + ) + + canvas.tag_bind(self._hit_id, "", self._on_click) + canvas.tag_bind(self._text_id, "", self._on_click) + + @property + def item_ids(self): + return [self._hit_id, self._text_id] + + def _on_click(self, event=None): + global _focused + if _focused and _focused is not self: + _focused._blur() + _focused = self + self.canvas.focus_set() + self.canvas.focus(self._text_id) + self.canvas.bind("", _dispatch_key) + if event: + idx = self.canvas.index(self._text_id, f"@{event.x},{event.y}") + self.canvas.icursor(self._text_id, idx) + else: + self.canvas.icursor(self._text_id, tk.END) + + def _blur(self): + global _focused + if _focused is self: + _focused = None + self.canvas.focus("") + + @classmethod + def blur_all(cls): + global _focused + if _focused: + _focused._blur() + + def get(self): + return self.canvas.itemcget(self._text_id, "text") + + def delete(self, start, end=None): + self.canvas.dchars(self._text_id, 0, tk.END) + + def insert(self, index, text): + self.canvas.insert(self._text_id, index, text) + + +def _dispatch_key(event): + if _focused: + if event.keysym == "BackSpace": + idx = _focused.canvas.index(_focused._text_id, tk.INSERT) + if idx > 0: + _focused.canvas.dchars(_focused._text_id, idx - 1, idx - 1) + elif event.char and event.char.isprintable(): + _focused.canvas.insert(_focused._text_id, tk.INSERT, event.char) diff --git a/src/screens/components/dev_overlay.py b/src/screens/components/dev_overlay.py new file mode 100644 index 0000000..fbc827c --- /dev/null +++ b/src/screens/components/dev_overlay.py @@ -0,0 +1,110 @@ +import tkinter as tk + +from screens.check_in_rfid import CheckInRFID +from screens.create_account_barcode import CreateAccountBarcode +from screens.create_account_manual import CreateAccountManual +from screens.sign_waiver import SignWaiver +from screens.check_in_manual import CheckInManual +from screens.qr_codes import QRCodes +from screens.user_welcome import UserWelcome + +_DEV_NAME = "Dev User" +_DEV_EMAIL = "devuser@ucsd.edu" +_DEV_PID = "A12345678" +_DEV_RFID = "1a2b3c4d5e6f7g" +_THANK_MSG = "Thank you for registering" + + +def _sim_no_account_success(nav): + nav.ctx.rfid = _DEV_RFID + def on_done(): + nav.ctx.traffic_light.request_green() + nav.get_frame(UserWelcome).display_name(_DEV_NAME, _THANK_MSG) + nav.go_to_create_account(on_done=on_done) + + +def _sim_no_account_needs_waiver(nav): + nav.ctx.rfid = _DEV_RFID + nav.go_to_create_account(on_done=nav.go_to_sign_waiver) + + +def _sim_fill_and_go(nav): + frm = nav.get_frame(CreateAccountManual) + frm.clear_entries() + frm.pid_entry.insert(0, _DEV_PID) + nav.go_to_create_account_manual() + + +TRANSITIONS = { + CheckInRFID: [ + ("QR Codes", lambda nav: nav.show_frame(QRCodes)), + ("No ID", lambda nav: nav.go_to_no_id()), + ("card: success", lambda nav: nav.get_frame(UserWelcome).display_name(_DEV_NAME)), + ("card: no account [→ success]", _sim_no_account_success), + ("card: no account [→ waiver]", _sim_no_account_needs_waiver), + ("card: no waiver", lambda nav: nav.go_to_sign_waiver()), + ], + QRCodes: [ + ("← Main", lambda nav: nav.back_to_main()), + ], + CheckInManual: [ + ("← Main", lambda nav: nav.back_to_main()), + ("PID: success", lambda nav: nav.get_frame(UserWelcome).display_name(_DEV_NAME)), + ("PID: no account [→ success]", _sim_no_account_success), + ("PID: no account [→ waiver]", _sim_no_account_needs_waiver), + ("PID: no waiver", lambda nav: nav.go_to_sign_waiver()), + ], + CreateAccountBarcode: [ + ("sim barcode swipe", _sim_fill_and_go), + ("manual fill", lambda nav: nav.go_to_create_account_manual()), + ("← Main", lambda nav: nav.back_to_main()), + ], + CreateAccountManual: [ + ("submit", lambda nav: nav.pop()), + ("← Main", lambda nav: nav.back_to_main()), + ], + SignWaiver: [ + ("← Main", lambda nav: nav.back_to_main()), + ], +} + + +class DevOverlay: + def __init__(self, canvas, nav): + self._canvas = canvas + self._nav = nav + self._buttons = [] + + self._frame = tk.Frame(canvas, bg="#1a1a2e", relief="solid", bd=1) + tk.Label( + self._frame, + text="DEV NAV", + bg="#1a1a2e", fg="#aaaaaa", + font=("Courier", 9, "bold"), + ).pack(pady=(4, 2), padx=6) + + self._canvas_window = canvas.create_window( + 1270, 715, anchor="se", window=self._frame, + ) + + def update(self, screen_class): + for btn in self._buttons: + btn.destroy() + self._buttons.clear() + + for label, action in TRANSITIONS.get(screen_class, []): + btn = tk.Label( + self._frame, + text=label, + bg="#2a2a4e", fg="white", + font=("Courier", 9), + padx=6, pady=3, + cursor="hand2", + ) + btn.pack(fill="x", padx=4, pady=1) + btn.bind("", lambda e, a=action: a(self._nav)) + btn.bind("", lambda e, w=btn: w.configure(bg="#4a4a8e")) + btn.bind("", lambda e, w=btn: w.configure(bg="#2a2a4e")) + self._buttons.append(btn) + + self._canvas.tag_raise(self._canvas_window) diff --git a/src/screens/create_account_barcode.py b/src/screens/create_account_barcode.py new file mode 100644 index 0000000..0aa7b01 --- /dev/null +++ b/src/screens/create_account_barcode.py @@ -0,0 +1,54 @@ +from pathlib import Path +from tkinter import Button +from .base import Screen + +ASSETS_PATH = Path(__file__).parent.parent / "assets" / "create_account_barcode" +SHARED_PATH = Path(__file__).parent.parent / "assets" / "shared" + + +class CreateAccountBarcode(Screen): + def _build(self, controller): + logo = self._photo(SHARED_PATH / "button_generic.png") + self._image(88.0, 90.0, image=logo) + + home_img = self._photo(SHARED_PATH / "icon_home.png") + home_btn = Button( + self.canvas, image=home_img, bg="#153246", + command=lambda: controller.back_to_main(), + relief="flat", highlightthickness=0, bd=0, + ) + self._window(53.0, 55.0, home_btn) + + outline1_img = self._photo(ASSETS_PATH / "outline_1.png") + self._image(640.0, 76.0, image=outline1_img) + + outline2_img = self._photo(ASSETS_PATH / "outline_2.png") + self._image(640.0, 430.0, image=outline2_img) + + icon_unchecked = self._photo(SHARED_PATH / "icon_unchecked_box.png") + self._image(576.0, 65.0, image=icon_unchecked) + + icon_checked = self._photo(SHARED_PATH / "icon_checked_box.png") + self._image(1030.0, 65.0, image=icon_checked) + + self._text( + 640.0, 374.0, anchor="center", + text="Please scan your ID barcode", + fill="#F5F0E6", font=("Montserrat", 48 * -1), + ) + self._text( + 215.0, 45.0, anchor="nw", + text="Account Status:", fill="#F5F0E6", font=("Montserrat", 40 * -1), + ) + self._text( + 690.0, 45.0, anchor="nw", + text="Waiver Status:", fill="#F5F0E6", font=("Montserrat", 40 * -1), + ) + + btn_img = self._photo(ASSETS_PATH / "button_fill_manually.png") + btn = Button( + self.canvas, image=btn_img, + borderwidth=0, highlightthickness=0, + command=lambda: controller.go_to_create_account_manual(), relief="flat", + ) + self._window(465.0, 554.0, btn, width=349, height=71) diff --git a/src/screens/create_account_manual.py b/src/screens/create_account_manual.py new file mode 100644 index 0000000..7e5e45a --- /dev/null +++ b/src/screens/create_account_manual.py @@ -0,0 +1,73 @@ +from pathlib import Path +from tkinter import Button, END +from .base import Screen +from .components.canvas_entry import CanvasEntry +import logging + +ASSETS_PATH = Path(__file__).parent.parent / "assets" / "create_account_manual" +SHARED_PATH = Path(__file__).parent.parent / "assets" / "shared" + + +class CreateAccountManual(Screen): + def _build(self, controller): + logo = self._photo(SHARED_PATH / "button_generic.png") + self._image(88.0, 90.0, image=logo) + + home_img = self._photo(SHARED_PATH / "icon_home.png") + home_btn = Button( + self.canvas, image=home_img, bg="#153246", + command=lambda: controller.back_to_main(), + relief="flat", highlightthickness=0, bd=0, + ) + self._window(53.0, 55.0, home_btn) + + outline1_img = self._photo(ASSETS_PATH / "outline_1.png") + self._image(640.0, 76.0, image=outline1_img) + + outline2_img = self._photo(ASSETS_PATH / "outline_2.png") + self._image(640.0, 430.0, image=outline2_img) + + icon_unchecked = self._photo(SHARED_PATH / "icon_unchecked_box.png") + self._image(605.0, 77.0, image=icon_unchecked) + self._image(1010.0, 77.0, image=icon_unchecked) + + field_img = self._photo(SHARED_PATH / "field.png") + self._image(640.0, 390.0, image=field_img) + + self._text( + 250.0, 45.0, anchor="nw", + text="Account Status:", fill="#F5F0E6", font=("Montserrat", 40 * -1), + ) + self._text( + 670.0, 45.0, anchor="nw", + text="Waiver Status:", fill="#F5F0E6", font=("Montserrat", 40 * -1), + ) + self._text( + 640.0, 340.0, anchor="center", + text="PID", fill="#F5F0E6", font=("Montserrat", 24 * -1), + ) + + btn_img = self._photo(ASSETS_PATH / "register.png") + btn = Button( + self.canvas, image=btn_img, + borderwidth=0, highlightthickness=0, + command=self._call_account_creation, relief="flat", + ) + self._window(465.0, 490.0, btn, width=349, height=71) + + self.pid_entry = self._canvas_entry(640.0, 390.0, w=800, h=44, font=("Montserrat", 20)) + + def hide(self): + CanvasEntry.blur_all() + super().hide() + + def clear_entries(self): + self.pid_entry.delete(0, END) + + def _call_account_creation(self): + pid = self.pid_entry.get() + self.clear_entries() + try: + self.controller.ctx.account.create_account_from_pid(pid) + except Exception: + logging.warning("Error occurred trying to create a user account", exc_info=True) diff --git a/src/screens/qr_codes.py b/src/screens/qr_codes.py new file mode 100644 index 0000000..974ded5 --- /dev/null +++ b/src/screens/qr_codes.py @@ -0,0 +1,35 @@ +from pathlib import Path +from tkinter import Button +from .base import Screen + +ASSETS_PATH = Path(__file__).parent.parent / "assets" / "qr_codes" +SHARED_PATH = Path(__file__).parent.parent / "assets" / "shared" + + +class QRCodes(Screen): + def _build(self, controller): + logo = self._photo(SHARED_PATH / "button_generic.png") + self._image(88.0, 90.0, image=logo) + + qr_website_img = self._photo(ASSETS_PATH / "qr_website.png") + self._image(421.0, 360.0, image=qr_website_img) + + qr_waiver_img = self._photo(ASSETS_PATH / "qr_waiver.png") + self._image(859.0, 360.0, image=qr_waiver_img) + + self._text( + 421.0, 571.0, anchor="center", + text="Website", fill="#F5F0E6", font=("Montserrat", 40 * -1), + ) + self._text( + 859.0, 571.0, anchor="center", + text="Waiver", fill="#F5F0E6", font=("Montserrat", 40 * -1), + ) + + btn_img = self._photo(SHARED_PATH / "icon_home.png") + btn = Button( + self.canvas, image=btn_img, bg="#153246", + command=lambda: controller.back_to_main(), + relief="flat", + ) + self._window(53.0, 55.0, btn) diff --git a/src/screens/sign_waiver.py b/src/screens/sign_waiver.py new file mode 100644 index 0000000..3924b90 --- /dev/null +++ b/src/screens/sign_waiver.py @@ -0,0 +1,49 @@ +from pathlib import Path +from tkinter import Button +from .base import Screen + +ASSETS_PATH = Path(__file__).parent.parent / "assets" / "sign_waiver" +SHARED_PATH = Path(__file__).parent.parent / "assets" / "shared" + + +class SignWaiver(Screen): + def _build(self, controller): + outline1_img = self._photo(ASSETS_PATH / "outline_1.png") + self._image(1042.0, 359.0, image=outline1_img) + + outline2_img = self._photo(ASSETS_PATH / "outline_2.png") + self._image(408.0, 76.0, image=outline2_img) + + outline3_img = self._photo(ASSETS_PATH / "outline_3.png") + self._image(408.0, 429.0, image=outline3_img) + + icon_checked = self._photo(SHARED_PATH / "icon_checked_box.png") + self._image(395.0, 70.0, image=icon_checked) + + icon_unchecked = self._photo(SHARED_PATH / "icon_unchecked_box.png") + self._image(750.0, 70.0, image=icon_unchecked) + + qr_waiver_img = self._photo(ASSETS_PATH / "qr_waiver.png") + self._image(1042.0, 328.0, image=qr_waiver_img) + + self._text( + 37.0, 45.0, anchor="nw", + text="Account Status:", fill="#F5F0E6", font=("Montserrat", 40 * -1), + ) + self._text( + 430.0, 45.0, anchor="nw", + text="Waiver Status:", fill="#F5F0E6", font=("Montserrat", 40 * -1), + ) + self._text( + 45.0, 270.0, anchor="nw", + text="Please scan the QR code\non the right and sign our \n waiver", + fill="#F5F0E6", font=("Montserrat", 48 * -1), + ) + + btn_img = self._photo(ASSETS_PATH / "button_done_scanning.png") + btn = Button( + self.canvas, image=btn_img, + borderwidth=0, highlightthickness=0, + command=lambda: controller.back_to_main(), relief="flat", + ) + self._window(875.0, 581.0, btn, width=344, height=71) diff --git a/src/screens/transition_screen.py b/src/screens/transition_screen.py new file mode 100644 index 0000000..b6df9ac --- /dev/null +++ b/src/screens/transition_screen.py @@ -0,0 +1,15 @@ +from .base import Screen + + +class TransitionScreen(Screen): + def _build(self, controller): + self._msg_id = self._text( + 640, 340, anchor="center", + text="", + fill="#F5F0E6", font=("Montserrat", 64 * -1), + justify="center", + ) + + def display(self, message): + self.canvas.itemconfigure(self._msg_id, text=message) + self.controller.show_frame(TransitionScreen) diff --git a/src/screens/UserWelcome.py b/src/screens/user_welcome.py similarity index 76% rename from src/screens/UserWelcome.py rename to src/screens/user_welcome.py index 387463f..3804eb3 100644 --- a/src/screens/UserWelcome.py +++ b/src/screens/user_welcome.py @@ -1,5 +1,4 @@ -from .screen import Screen -import global_ +from .base import Screen class UserWelcome(Screen): @@ -7,7 +6,7 @@ def _build(self, controller): self.last_name = None self.offset = 0 - self._text( + self._msg_item = self._text( 99.33203125, 259.33203125, anchor="nw", text="Welcome back", fill="#F5F0E6", font=("Montserrat", 45 * -1), @@ -15,19 +14,18 @@ def _build(self, controller): def hide(self): super().hide() - # Clean up any dynamic name items when leaving this screen self.canvas.delete("welcome") + self.canvas.itemconfigure(self._msg_item, text="Welcome back") self.last_name = None self.offset = 0 - def displayName(self, name): + def display_name(self, name, message="Welcome back"): if name == self.last_name: return self.last_name = name - - from .MainPage import MainPage - global_.app.show_frame(UserWelcome) + self.canvas.itemconfigure(self._msg_item, text=message) + self.controller.show_frame(UserWelcome) text_id = self.canvas.create_text( 99.0, @@ -43,7 +41,6 @@ def displayName(self, name): self.canvas.after(3000, lambda: self._remove_name(text_id)) def _remove_name(self, text_id): - from .MainPage import MainPage self.canvas.delete(text_id) self.offset -= 73 @@ -54,5 +51,4 @@ def _remove_name(self, text_id): if not self.canvas.find_withtag("welcome"): self.last_name = None - global_.traffic_light.set_off() - global_.app.show_frame(MainPage) + self.controller.back_to_main() diff --git a/src/swipe.py b/src/swipe.py deleted file mode 100644 index a212fc0..0000000 --- a/src/swipe.py +++ /dev/null @@ -1,143 +0,0 @@ -import tkinter -from screens.ManualFill import ManualFill -from screens.NoAccNoWaiverSwipe import NoAccNoWaiverSwipe -from screens.WaiverNoAccSwipe import WaiverNoAccSwipe -from screens.AccNoWaiverSwipe import AccNoWaiverSwipe -from screens.CheckInNoId import CheckInNoId -from get_info_from_pid import contact_client -from utils import utils -import global_ -import logging - -############################################ -# This class helps handle reading magswipe # -############################################ - -swipe_error_shown = False - - -class swipe: - def __init__(self): - global id_string - id_string = "" - - def keyboardPress(self, key): - util = utils() - global id_string, swipe_error_shown - curr_frame = global_.app.get_curr_frame() - - if curr_frame not in (NoAccNoWaiverSwipe, WaiverNoAccSwipe, CheckInNoId): - return - - id_string += key.char - logging.debug("The array is now: " + repr(str(id_string))) - - if id_string.endswith("\r"): - if util.IDVet(id_string) == "bad": - id_string = "" - if not swipe_error_shown: - swipe_error_shown = True - canvas = global_.app.canvas - id_error = tkinter.Label( - canvas, text="Error, please scan again", - bg="#153246", fg="white", font=("Arial", 20), - ) - id_error.place(relx=0.5, rely=0.85, anchor="center") - id_error_2 = id_error # single label serves both swipe screens - id_error.after(1500, lambda: self.destroySwipeError(id_error)) - id_error_2.after(1500, lambda: self.destroySwipeError(id_error_2)) - return - - self.swipeCard(id_string) - id_string = "" - - def pullUser(self, barcode, u_type): - # This function takes in the User's ID and - # if they are a Student or Staff - # and runs David's query funciton accordingly - # It returns a list containing: - # [fname, lname, [emails]] - u_info = [] - - logging.info(f"Card barcode read is: {barcode}. Trying to pull user...") - - contact = contact_client() - try: - if u_type == "Staff": - u_info = contact.get_staff_info(barcode) - elif u_type == "Student": - u_info = contact.get_student_info(barcode) - except Exception as e: - logging.warning( - "An exception has ocurred with pulling user information", exc_info=True - ) - return None - if not u_info: - logging.info("Student search returned False, returning...") - return - - logging.info(f"Info pull succeeded:\n {u_info[0]}, {u_info[1]}, {u_info[3]}") - return u_info - - def swipeCard(self, id_string): - # Grabs the input from the global swipe entry - # Deletes text from the entry box - # Checks if any of the ID is a letter - # If so return - # Calls magswipe() on the entered string - - user_card_number = id_string.strip() - - # u_info = self.magSwipe(id_string) - - # u_type = u_info[0] - # u_id = u_info[1] - # u_id = u_id.replace("+E?", "")[:9] - - # u_data is a list containing the user type and their ID - u_data = self.pullUser(user_card_number, "Student") - if not u_data: - logging.info("Student search returned False, returning...") - return - # if u_type == "Student": - # u_id = "A" + u_id - if global_.app.get_curr_frame() == CheckInNoId: - global_.app.get_frame(CheckInNoId).clearEntries() - global_.app.get_frame(CheckInNoId).updateEntries(u_data[3]) - return - - email_to_use = "" if len(u_data[2]) == 0 else u_data[2][0] - for email in u_data[2]: - if email.endswith("@ucsd.edu"): - email_to_use = email - - manfill = global_.app.get_frame(ManualFill) - manfill.clearEntries() - logging.info( - f"Filling data with {u_data[0]} {u_data[1]} {email_to_use} {u_data[3]}" - ) - manfill.updateEntries(u_data[0], u_data[1], email_to_use, u_data[3]) - - global_.app.show_frame(ManualFill) - - def magSwipe(self, ID): - # Makes a new empty string - # Takes only chars 3-11 from the card swipe text - # Returns student or staff ID - - u_type = "" - - if ID[2] == "9": - u_type = "Student" - elif ID[2] == "0": - u_type = "Staff" - - s = "" - for c in range(3, 11): - s += ID[c] - return [u_type, s] - - def destroySwipeError(self, id_error): - global swipe_error_shown - id_error.destroy() - swipe_error_shown = False diff --git a/src/utils.py b/src/utils.py deleted file mode 100644 index 4f2fa0a..0000000 --- a/src/utils.py +++ /dev/null @@ -1,130 +0,0 @@ -from datetime import datetime -import time -import global_ -import tkinter -from screens.MainPage import MainPage -from screens.AccNoWaiverSwipe import AccNoWaiverSwipe -from screens.UserThank import UserThank -import logging - -###################################################### -# Utilities that I couldn't get to fit anywhere else # -###################################################### - - -class utils: - def __init__(self) -> None: - pass - - def emailCheck(self, email): - validations = ( - (lambda s: "@" in s, "Email is invalid"), - (lambda s: "." in s, "Email is invalid"), - ) - - for valid, message in validations: - if not valid(email): - return message - - return "good" - - def nameCheck(self, fname, lname): - if len(fname) == 0 or len(lname) == 0: - return "Name was not entered" - - return "good" - - def IDCheck(self, user_id): - if len(user_id) <= 2 or len(user_id) > 12: - return "PID was not entered correctly" - return "good" - - def IDVet(self, id_check): - if any(i.isalpha() for i in id_check): - return "bad" - - if len(id_check) >= 16: - return "bad" - - return "good" - - def getDatetime(self): - return datetime.now().strftime("%m/%d/%Y %H:%M:%S") - - def createAccount(self, fname, lname, email, pid, ManualFill): - start = time.perf_counter() - idValid = self.IDCheck(pid) - emailValid = self.emailCheck(email) - nameValid = self.nameCheck(fname, lname) - - canvas = global_.app.canvas - - for validation in (idValid, emailValid, nameValid): - if validation != "good": - invalidID = tkinter.Label( - canvas, text=validation, bg="#153246", fg="white", font=("Arial", 20) - ) - invalidID.place(relx=0.5, rely=0.83, anchor="center") - invalidID.after(3000, lambda: invalidID.destroy()) - return - - end1 = time.perf_counter() - logging.debug(f"Time to validate info: {end1 - start}") - - inProgress = tkinter.Label( - canvas, - text="Account creation in progress!", - bg="#153246", fg="white", font=("Arial", 25), - ) - inProgress.place(relx=0.5, rely=0.87, anchor="center") - global_.app.update() - - full_name = fname + " " + lname - logging.info(f"Creating user account for {full_name}") - - no_wifi = tkinter.Label( - canvas, - text="ERROR! Connection cannot be established, please let staff know.", - bg="#153246", fg="white", font=("Arial", 25), - ) - - end2 = time.perf_counter() - logging.debug(f"Time to structure row entries: {end2 - end1}") - - retries = 1 - while retries < 6: - try: - result = global_.sheets.create_account(fname, lname, email, pid, global_.rfid) - end3 = time.perf_counter() - logging.debug(f"Time to create account: {end3 - end2}") - - if result is None: - raise Exception("Account creation returned no result") - - break - except Exception as e: - logging.warning("Exception occurred while in account creation") - logging.exception("Exception occurred while in account creation") - no_wifi.place(relx=0.5, rely=0.91, anchor="center") - global_.app.update() - time.sleep(retries) - retries += 1 - - no_wifi.destroy() - - if retries == 6: - global_.app.show_frame(MainPage) - inProgress.destroy() - return - - end4 = time.perf_counter() - logging.debug(f"Total time to send data: {end4 - end2}") - - checkin_result = global_.sheets.checkin_by_uuid(global_.rfid) - toGoTo = AccNoWaiverSwipe if checkin_result.get("status") == "no_waiver" else MainPage - - end5 = time.perf_counter() - logging.debug(f"Time to check waiver via check-in: {end5 - end4}") - - global_.app.get_frame(UserThank).displayName(full_name, toGoTo) - inProgress.destroy() diff --git a/src/window.py b/src/window.py new file mode 100644 index 0000000..23be69a --- /dev/null +++ b/src/window.py @@ -0,0 +1,38 @@ +import tkinter as tk +from pathlib import Path + +ASSETS_PATH = Path(__file__).parent / "assets" / "shared" + + +class CheckInWindow(tk.Tk): + def __init__(self): + super().__init__() + self.title("Check-In") + self.geometry("1280x720") + self.bind("", self._on_map) + + self.canvas = tk.Canvas( + self, + bg="#153246", + height=720, + width=1280, + bd=0, + highlightthickness=0, + ) + self.canvas.pack(fill="both", expand=True) + + self._bg_photos = [] + bg1 = tk.PhotoImage(file=str(ASSETS_PATH / "background_main.png")) + self._bg_photos.append(bg1) + self.canvas.create_image(640.0, 360.0, image=bg1) + + bg2 = tk.PhotoImage(file=str(ASSETS_PATH / "outline_full.png")) + self._bg_photos.append(bg2) + self.canvas.create_image(639.333984375, 359.333984375, image=bg2) + + def _on_map(self, event): + self.unbind("") + self.attributes("-fullscreen", True) + + def start(self): + self.mainloop() diff --git a/test.py b/test.py deleted file mode 100644 index adadd57..0000000 --- a/test.py +++ /dev/null @@ -1,11 +0,0 @@ -from tkinter import Tk, Label - -root=Tk() - -def key_pressed(event): - print(event.char) - return - -root.bind("",key_pressed) - -root.mainloop() \ No newline at end of file