""" This file is part of the SplendidBear Websites' projects. Copyright (c) 2026 @ www.splendidbear.org For the full copyright and license information, please view the LICENSE file that was distributed with this source code. """ from __future__ import annotations import threading from collections.abc import Callable import gi gi.require_version("Gtk", "4.0") gi.require_version("Adw", "1") from gi.repository import Gtk, Adw, GLib from mineseeker.api.auth import login, login_as_guest, TotpRequired, AuthError from mineseeker import assets class LoginPage(Gtk.Box): """Username + password login form with a 'Play as Guest' option.""" def __init__( self, on_success: Callable[[bool], None], on_guest: Callable[[], None], ) -> None: super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=0) self._on_success = on_success self._on_guest = on_guest self.set_valign(Gtk.Align.CENTER) self.set_halign(Gtk.Align.CENTER) clamp = Adw.Clamp() clamp.set_maximum_size(360) inner = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=16) inner.set_margin_top(32) inner.set_margin_bottom(32) inner.set_margin_start(16) inner.set_margin_end(16) # Title title = Gtk.Label(label="MineSeeker") title.add_css_class("title-1") inner.append(title) subtitle = Gtk.Label(label="Sign in to play") subtitle.add_css_class("dim-label") inner.append(subtitle) # Credentials group group = Adw.PreferencesGroup() self._username_row = Adw.EntryRow(title="Username") group.add(self._username_row) self._password_row = Adw.PasswordEntryRow(title="Password") self._password_row.connect("entry-activated", self._on_login_clicked) group.add(self._password_row) inner.append(group) # Error label self._error_label = Gtk.Label(label="") self._error_label.add_css_class("error") self._error_label.set_visible(False) inner.append(self._error_label) # Login button self._login_btn = Gtk.Button(label="Sign In") self._login_btn.add_css_class("suggested-action") self._login_btn.add_css_class("pill") self._login_btn.connect("clicked", self._on_login_clicked) inner.append(self._login_btn) # Separator sep = Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL) inner.append(sep) # Guest button guest_btn = Gtk.Button(label="Play as Guest") guest_btn.add_css_class("pill") guest_btn.connect("clicked", self._on_guest_clicked) inner.append(guest_btn) clamp.set_child(inner) self.append(clamp) def _set_busy(self, busy: bool) -> None: self._login_btn.set_sensitive(not busy) self._username_row.set_sensitive(not busy) self._password_row.set_sensitive(not busy) if busy: self._error_label.set_visible(False) def _show_error(self, message: str) -> None: self._error_label.set_label(message) self._error_label.set_visible(True) def _on_login_clicked(self, *_) -> None: username = self._username_row.get_text().strip() password = self._password_row.get_text() if not username or not password: self._show_error("Please enter username and password.") return self._set_busy(True) threading.Thread( target=self._do_login, args=(username, password), daemon=True ).start() def _do_login(self, username: str, password: str) -> None: try: login(username, password) # Load assets after successful authentication assets.load_sounds() GLib.idle_add(self._on_success, False) except TotpRequired: assets.load_sounds() GLib.idle_add(self._on_success, True) except AuthError as e: GLib.idle_add(self._handle_auth_error, str(e)) except Exception as e: GLib.idle_add(self._handle_auth_error, f"Connection error: {e}") def _handle_auth_error(self, message: str) -> bool: self._set_busy(False) self._show_error(message) return GLib.SOURCE_REMOVE def _on_guest_clicked(self, *_) -> None: self._set_busy(True) threading.Thread(target=self._do_guest, daemon=True).start() def _do_guest(self) -> None: try: login_as_guest() assets.load_sounds() GLib.idle_add(self._on_guest) except Exception as e: GLib.idle_add(self._handle_auth_error, f"Could not start guest session: {e}")