diff --git a/src/main/java/cz/jzitnik/Main.java b/src/main/java/cz/jzitnik/Main.java index 9f00aec..ded2d7b 100644 --- a/src/main/java/cz/jzitnik/Main.java +++ b/src/main/java/cz/jzitnik/Main.java @@ -19,8 +19,22 @@ import java.util.Optional; import cz.jzitnik.query.QueryClient; import cz.jzitnik.query.QueryOptions; +/** + * The main entry point for the JecnaClient desktop application. + * This class initializes the application's UI theme, sets up the navigation router, + * and handles the initial application state, including attempting to restore + * a user's session from previously saved credentials. + */ public class Main extends Application { + /** + * Initializes the JavaFX application, configures the UI, and sets up the routing system. + * It attempts to automatically log the user in if stored credentials are available, + * navigating to the dashboard on success or to the login screen if authentication fails. + * + * @param stage the primary window for the application. + * @throws Exception if an error occurs during initialization. + */ @Override public void start(Stage stage) throws Exception { Application.setUserAgentStylesheet(new PrimerDark().getUserAgentStylesheet()); @@ -77,6 +91,11 @@ public class Main extends Application { } } + /** + * Standard entry point for launching the JavaFX application. + * + * @param args command-line arguments passed to the application. + */ public static void main(String[] args) { launch(args); } diff --git a/src/main/java/cz/jzitnik/auth/CredentialStore.java b/src/main/java/cz/jzitnik/auth/CredentialStore.java index 0c9c683..60ebe5b 100644 --- a/src/main/java/cz/jzitnik/auth/CredentialStore.java +++ b/src/main/java/cz/jzitnik/auth/CredentialStore.java @@ -1,7 +1,3 @@ -/* -AI GENERATED SLOP -*/ - package cz.jzitnik.auth; import javax.crypto.Cipher; @@ -19,6 +15,9 @@ import java.util.EnumSet; import java.util.Optional; import java.util.Set; +/** + * Securely stores and retrieves user credentials using AES-GCM encryption. + */ public class CredentialStore { private static final Path DIR = Paths.get(System.getProperty("user.home"), ".jecnaclient"); private static final Path KEY_FILE = DIR.resolve("secret.key"); @@ -26,16 +25,32 @@ public class CredentialStore { private static final int KEY_SIZE = 256; private static final SecureRandom RAND = new SecureRandom(); + /** + * Represents user credentials (username and password). + */ public static final class Credentials { public final String username; public final String password; + /** + * Creates a new Credentials object. + * + * @param u The username. + * @param p The password. + */ public Credentials(String u, String p) { this.username = u; this.password = p; } } + /** + * Saves user credentials to a secure file. + * + * @param username The username to save. + * @param password The password to save. + * @throws Exception If an error occurs during saving. + */ public static void saveCredentials(String username, String password) throws Exception { if (username == null || password == null) return; ensureDir(); @@ -60,6 +75,12 @@ public class CredentialStore { trySetOwnerOnly(CRED_FILE); } + /** + * Loads user credentials from the secure file. + * + * @return An Optional containing the credentials if found and decrypted successfully, or empty otherwise. + * @throws Exception If an error occurs during loading or decryption. + */ public static Optional loadCredentials() throws Exception { if (!Files.exists(CRED_FILE)) return Optional.empty(); if (!Files.exists(KEY_FILE)) return Optional.empty(); @@ -87,12 +108,18 @@ public class CredentialStore { return Optional.of(new Credentials(u, p)); } + /** + * Clears stored credentials by deleting the encrypted file. + */ public static void clearCredentials() { try { Files.deleteIfExists(CRED_FILE); } catch (IOException ignored) {} } + /** + * Ensures the credentials directory exists with appropriate permissions. + */ private static void ensureDir() throws IOException { if (!Files.exists(DIR)) { Files.createDirectories(DIR); @@ -100,6 +127,9 @@ public class CredentialStore { } } + /** + * Loads the encryption key or creates a new one if it doesn't exist. + */ private static SecretKey loadOrCreateKey() throws Exception { if (Files.exists(KEY_FILE)) { byte[] k = Files.readAllBytes(KEY_FILE); @@ -113,6 +143,9 @@ public class CredentialStore { return key; } + /** + * Best-effort attempt to set POSIX file permissions to owner-only. + */ private static void trySetOwnerOnly(Path p) { try { // POSIX systems diff --git a/src/main/java/cz/jzitnik/controllers/AbsencesController.java b/src/main/java/cz/jzitnik/controllers/AbsencesController.java index c211bfd..2465bb6 100644 --- a/src/main/java/cz/jzitnik/controllers/AbsencesController.java +++ b/src/main/java/cz/jzitnik/controllers/AbsencesController.java @@ -18,6 +18,10 @@ import java.util.Comparator; import java.util.List; import java.util.Map; +/** + * Controller for handling the absences view. + * Displays student absences, including a summary and detailed breakdown by date. + */ @Route(path = "/absences", fxml = "/absences.fxml") public class AbsencesController extends DashboardBaseController { @@ -35,12 +39,22 @@ public class AbsencesController extends DashboardBaseController { private AbsencesPage currentPage; + /** + * Initializes the controller when navigating to this view. + * Initiates loading of absence data. + * + * @param props Navigation properties, not explicitly used here. + */ @Override public void onNavigate(Map props) { super.onNavigate(props); loadAbsences(); } + /** + * Fetches the absences page from the API and updates the UI. + * Shows a loading indicator while fetching. + */ private void loadAbsences() { loadingIndicator.setVisible(true); scrollPane.setVisible(false); @@ -57,6 +71,11 @@ public class AbsencesController extends DashboardBaseController { }, QueryOptions.defaultOptions()).thenAccept(this::handleAbsencesResult); } + /** + * Handles the result of the absences fetch operation. + * + * @param result The result of the fetch. + */ private void handleAbsencesResult(QueryResult result) { Platform.runLater(() -> { loadingIndicator.setVisible(false); @@ -75,6 +94,9 @@ public class AbsencesController extends DashboardBaseController { }); } + /** + * Renders the absence summary and detail cards. + */ private void renderAbsences() { summaryBox.getChildren().clear(); detailFlow.getChildren().clear(); @@ -111,6 +133,14 @@ public class AbsencesController extends DashboardBaseController { ); } + /** + * Creates a summary card UI component. + * + * @param title The title of the summary. + * @param count The numerical value to display. + * @param styleClass The CSS style class to apply for coloring. + * @return The created VBox component. + */ private VBox createSummaryCard(String title, int count, String styleClass) { VBox card = new VBox(5); card.setAlignment(Pos.CENTER); @@ -126,6 +156,13 @@ public class AbsencesController extends DashboardBaseController { return card; } + /** + * Creates an absence detail card UI component for a specific day. + * + * @param day The date of the absence. + * @param info The absence information for the day. + * @return The created VBox component. + */ private VBox createAbsenceCard(LocalDate day, AbsenceInfo info) { VBox card = new VBox(8); card.getStyleClass().addAll("card", "absence-card"); @@ -162,11 +199,20 @@ public class AbsencesController extends DashboardBaseController { return card; } + /** + * Formats a date for display. + * + * @param date The date to format. + * @return The formatted date string. + */ @SuppressWarnings("deprecation") private String formatDate(LocalDate date) { return date.getDayOfMonth() + ". " + date.getMonthNumber() + ". " + date.getYear(); } + /** + * Handles navigation back to the dashboard. + */ @FXML protected void onBackToDashboard() { Router.getInstance().navigate("/dashboard"); diff --git a/src/main/java/cz/jzitnik/controllers/ClassroomDetailController.java b/src/main/java/cz/jzitnik/controllers/ClassroomDetailController.java index c9a2c38..5d787bf 100644 --- a/src/main/java/cz/jzitnik/controllers/ClassroomDetailController.java +++ b/src/main/java/cz/jzitnik/controllers/ClassroomDetailController.java @@ -12,6 +12,11 @@ import javafx.scene.layout.*; import java.util.Map; +/** + * Controller responsible for displaying detailed information about a specific classroom. + * This includes fetching room data from the Jecna API and rendering the room's details, + * such as its name, code, floor, and timetable. + */ @Route(path = "/classroom_detail", fxml = "/classroom_detail.fxml") public class ClassroomDetailController extends DashboardBaseController { @@ -32,6 +37,11 @@ public class ClassroomDetailController extends DashboardBaseController { private String roomCode; + /** + * Initializes the view with the specified room code. + * + * @param props a map containing the "code" of the room to display. + */ @Override public void onNavigate(Map props) { super.onNavigate(props); @@ -45,6 +55,12 @@ public class ClassroomDetailController extends DashboardBaseController { loadRoom(); } + @FXML + @Override + protected void onBack() { + Router.getInstance().navigate("/classrooms"); + } + private void loadRoom() { loadingIndicator.setVisible(true); scrollPane.setVisible(false); @@ -121,10 +137,4 @@ public class ClassroomDetailController extends DashboardBaseController { return row + 1; } - - @FXML - @Override - protected void onBack() { - Router.getInstance().navigate("/classrooms"); - } } diff --git a/src/main/java/cz/jzitnik/controllers/ClassroomsController.java b/src/main/java/cz/jzitnik/controllers/ClassroomsController.java index efe6666..e7b52d6 100644 --- a/src/main/java/cz/jzitnik/controllers/ClassroomsController.java +++ b/src/main/java/cz/jzitnik/controllers/ClassroomsController.java @@ -16,6 +16,11 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; +/** + * Controller for the classrooms view, which displays a list of available classrooms. + * Users can search and filter classrooms, and clicking a classroom navigates + * to its detailed view. + */ @Route(path = "/classrooms", fxml = "/classrooms.fxml") public class ClassroomsController extends DashboardBaseController { @@ -33,6 +38,11 @@ public class ClassroomsController extends DashboardBaseController { private List allRooms = new ArrayList<>(); + /** + * Initializes the view, sets up the search filter, and triggers loading of the classrooms. + * + * @param props navigation properties. + */ @Override public void onNavigate(Map props) { super.onNavigate(props); @@ -115,6 +125,9 @@ public class ClassroomsController extends DashboardBaseController { } } + /** + * Navigates back to the dashboard. + */ @FXML protected void onBackToDashboard() { Router.getInstance().navigate("/dashboard"); diff --git a/src/main/java/cz/jzitnik/controllers/DashboardBaseController.java b/src/main/java/cz/jzitnik/controllers/DashboardBaseController.java index 01760d2..d1c476a 100644 --- a/src/main/java/cz/jzitnik/controllers/DashboardBaseController.java +++ b/src/main/java/cz/jzitnik/controllers/DashboardBaseController.java @@ -12,6 +12,10 @@ import cz.jzitnik.router.Router; import javafx.fxml.FXML; import javafx.scene.control.Button; +/** + * Base controller for dashboard views. + * Handles common functionality such as navigation and loading user profile information. + */ public class DashboardBaseController implements Routable { @InjectState @@ -48,9 +52,18 @@ public class DashboardBaseController implements Routable { protected void onDoNothing() { } + /** + * Handles navigation back to the home view. + */ @FXML protected void onBack() { Router.getInstance().navigate("/home"); } + /** + * Called when navigating to a dashboard view. + * Initializes the welcome and class labels and fetches user profile data. + * + * @param props Navigation properties. + */ @Override public void onNavigate(Map props) { if (welcomeLabel != null && classLabel != null) { diff --git a/src/main/java/cz/jzitnik/controllers/DashboardController.java b/src/main/java/cz/jzitnik/controllers/DashboardController.java index 7eb1aed..3845410 100644 --- a/src/main/java/cz/jzitnik/controllers/DashboardController.java +++ b/src/main/java/cz/jzitnik/controllers/DashboardController.java @@ -7,14 +7,26 @@ import javafx.fxml.FXML; import java.util.Map; +/** + * Controller for handling the main dashboard view. + */ @Route(path = "/dashboard", fxml = "/dashboard_modern.fxml") public class DashboardController extends DashboardBaseController { + /** + * Called when navigating to the dashboard view. + * + * @param props Navigation properties. + */ @Override public void onNavigate(Map props) { super.onNavigate(props); } + /** + * Handles the logout action. + * Clears application state, credentials, and navigates back to the login screen. + */ @FXML public void onLogoutClick() { appState.clear(); diff --git a/src/main/java/cz/jzitnik/controllers/GradesController.java b/src/main/java/cz/jzitnik/controllers/GradesController.java index ff95a91..0d248f0 100644 --- a/src/main/java/cz/jzitnik/controllers/GradesController.java +++ b/src/main/java/cz/jzitnik/controllers/GradesController.java @@ -20,6 +20,10 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +/** + * Controller for handling the grades view. + * Displays student grades grouped by subject and allows grade prediction. + */ @Route(path = "/grades", fxml = "/grades.fxml") public class GradesController extends DashboardBaseController { @@ -35,8 +39,20 @@ public class GradesController extends DashboardBaseController { private GradesPage currentPage; private final Map> predictions = new HashMap<>(); + /** + * Represents a predicted grade. + * + * @param value The grade value (1-5). + * @param isSmall Whether the grade has a smaller weight. + */ public record PredictedGrade(int value, boolean isSmall) {} + /** + * Initializes the controller when navigating to this view. + * Clears previous predictions and initiates loading of grades data. + * + * @param props Navigation properties, not explicitly used here. + */ @Override public void onNavigate(Map props) { super.onNavigate(props); @@ -44,6 +60,10 @@ public class GradesController extends DashboardBaseController { loadGrades(); } + /** + * Fetches the grades page from the API and updates the UI. + * Shows a loading indicator while fetching. + */ private void loadGrades() { loadingIndicator.setVisible(true); scrollPane.setVisible(false); @@ -59,6 +79,11 @@ public class GradesController extends DashboardBaseController { }, QueryOptions.defaultOptions()).thenAccept(this::handleGradesResult); } + /** + * Handles the result of the grades fetch operation. + * + * @param result The result of the fetch. + */ private void handleGradesResult(QueryResult result) { Platform.runLater(() -> { loadingIndicator.setVisible(false); @@ -78,6 +103,9 @@ public class GradesController extends DashboardBaseController { }); } + /** + * Renders all subjects and their grades. + */ private void renderAllSubjects() { contentBox.getChildren().clear(); contentBox.setAlignment(Pos.TOP_CENTER); @@ -95,6 +123,13 @@ public class GradesController extends DashboardBaseController { } } + /** + * Calculates the average grade for a subject, including predictions. + * + * @param subject The subject. + * @param preds List of predicted grades. + * @return The calculated average, or null if no grades. + */ private Float calculateAverage(Subject subject, List preds) { float sum = 0; float count = 0; @@ -122,6 +157,12 @@ public class GradesController extends DashboardBaseController { return count > 0 ? sum / count : null; } + /** + * Creates a subject card UI component. + * + * @param subject The subject. + * @return The created VBox component. + */ private VBox createSubjectCard(Subject subject) { String subjectNameFull = subject.getName().getFull(); List preds = predictions.getOrDefault(subjectNameFull, new ArrayList<>()); @@ -208,6 +249,12 @@ public class GradesController extends DashboardBaseController { return card; } + /** + * Shows a dialog to add a predicted grade. + * + * @param subjectNameFull Full subject name. + * @param preds List of existing predictions for the subject. + */ private void showPredictionDialog(String subjectNameFull, List preds) { Dialog dialog = new Dialog<>(); dialog.setTitle("Nová predikce"); @@ -247,6 +294,18 @@ public class GradesController extends DashboardBaseController { }); } + /** + * Creates a grade badge UI component. + * + * @param value The grade value. + * @param isSmall Whether it's a small grade. + * @param valueChar The character representation of the grade (if not numeric). + * @param isPredicted Whether this is a predicted grade. + * @param desc Description of the grade. + * @param teacher Teacher who gave the grade. + * @param date Date the grade was received. + * @return The created StackPane component. + */ private StackPane createGradeBadge(int value, boolean isSmall, char valueChar, boolean isPredicted, String desc, String teacher, String date) { StackPane badge = new StackPane(); String valStr = String.valueOf(valueChar); @@ -302,6 +361,13 @@ public class GradesController extends DashboardBaseController { return badge; } + /** + * Shows a dialog with grade details. + * + * @param desc Description of the grade. + * @param teacher Teacher who gave the grade. + * @param date Date the grade was received. + */ private void showGradeDetailsDialog(String desc, String teacher, String date) { Dialog dialog = new Dialog<>(); dialog.setTitle("Detail známky"); @@ -325,6 +391,9 @@ public class GradesController extends DashboardBaseController { dialog.show(); } + /** + * Handles navigation back to the dashboard. + */ @FXML protected void onBackToDashboard() { Router.getInstance().navigate("/dashboard"); diff --git a/src/main/java/cz/jzitnik/controllers/HomeController.java b/src/main/java/cz/jzitnik/controllers/HomeController.java index d977025..8be2dee 100644 --- a/src/main/java/cz/jzitnik/controllers/HomeController.java +++ b/src/main/java/cz/jzitnik/controllers/HomeController.java @@ -10,6 +10,9 @@ import javafx.scene.control.Label; import java.util.Map; +/** + * Controller for handling the home view. + */ @Route(path = "/home", fxml = "/home.fxml") public class HomeController implements Routable { @@ -19,6 +22,12 @@ public class HomeController implements Routable { @FXML private Label welcomeLabel; + /** + * Called when navigating to the home view. + * Updates the welcome label with the username. + * + * @param props Navigation properties. + */ @Override public void onNavigate(Map props) { String username = appState.getUsername(); @@ -27,6 +36,9 @@ public class HomeController implements Routable { welcomeLabel.setText("Welcome, " + username + "!"); } + /** + * Handles the logout action. + */ @FXML public void onLogoutClick() { appState.clear(); diff --git a/src/main/java/cz/jzitnik/controllers/LoginController.java b/src/main/java/cz/jzitnik/controllers/LoginController.java index 186d626..cb045ed 100644 --- a/src/main/java/cz/jzitnik/controllers/LoginController.java +++ b/src/main/java/cz/jzitnik/controllers/LoginController.java @@ -11,6 +11,9 @@ import javafx.scene.control.*; import java.util.Map; import javafx.application.Platform; +/** + * Controller for handling the login view. + */ @Route(path = "/login", fxml = "/login.fxml") public class LoginController implements Routable { @@ -29,10 +32,18 @@ public class LoginController implements Routable { @FXML private ProgressIndicator loadingIndicator; + /** + * Called when navigating to the login view. + * + * @param props Navigation properties. + */ @Override public void onNavigate(Map props) { } + /** + * Handles the login action. Initiates the login process in a background thread. + */ @FXML public void onLoginClick() { final String username = usernameField.getText(); @@ -63,6 +74,9 @@ public class LoginController implements Routable { }, "login-thread").start(); } + /** + * Handles username action (e.g., pressing enter) by requesting focus on the password field. + */ @FXML public void onUsernameAction() { passwordField.requestFocus(); diff --git a/src/main/java/cz/jzitnik/controllers/MoodleController.java b/src/main/java/cz/jzitnik/controllers/MoodleController.java index b02ef70..a91a2b3 100644 --- a/src/main/java/cz/jzitnik/controllers/MoodleController.java +++ b/src/main/java/cz/jzitnik/controllers/MoodleController.java @@ -13,6 +13,10 @@ import javafx.scene.web.WebView; import java.util.Map; import java.util.Objects; +/** + * Controller for handling the Moodle integration view. + * Displays a WebView with the Moodle website and handles automatic login. + */ @Route(path = "/moodle", fxml = "/moodle.fxml") public class MoodleController extends DashboardBaseController { @@ -33,6 +37,12 @@ public class MoodleController extends DashboardBaseController { private WebEngine webEngine; + /** + * Initializes the controller when navigating to this view. + * Sets up the WebView, auto-login logic, and navigation buttons. + * + * @param props Navigation properties, not explicitly used here. + */ @Override public void onNavigate(Map props) { super.onNavigate(props); @@ -104,6 +114,9 @@ public class MoodleController extends DashboardBaseController { webEngine.load("https://moodle.spsejecna.cz/"); } + /** + * Updates the back and forward navigation buttons state. + */ private void updateNavigationButtons() { WebHistory history = webEngine.getHistory(); int currentIndex = history.getCurrentIndex(); @@ -129,6 +142,9 @@ public class MoodleController extends DashboardBaseController { btnForward.setDisable(validForwardIndex == -1); } + /** + * Navigates back in the WebView history. + */ @FXML protected void onBrowserBack() { WebHistory history = webEngine.getHistory(); @@ -142,6 +158,9 @@ public class MoodleController extends DashboardBaseController { } } + /** + * Navigates forward in the WebView history. + */ @FXML protected void onBrowserForward() { WebHistory history = webEngine.getHistory(); @@ -155,11 +174,17 @@ public class MoodleController extends DashboardBaseController { } } + /** + * Reloads the current page in the WebView. + */ @FXML protected void onBrowserReload() { webEngine.reload(); } + /** + * Handles navigation back to the dashboard. + */ @FXML protected void onBackToDashboard() { Router.getInstance().navigate("/dashboard"); diff --git a/src/main/java/cz/jzitnik/controllers/TeacherProfileController.java b/src/main/java/cz/jzitnik/controllers/TeacherProfileController.java index f5408d3..cae06c9 100644 --- a/src/main/java/cz/jzitnik/controllers/TeacherProfileController.java +++ b/src/main/java/cz/jzitnik/controllers/TeacherProfileController.java @@ -15,6 +15,10 @@ import javafx.scene.layout.*; import java.util.Map; +/** + * Controller for handling the teacher profile view. + * Displays details about a specific teacher, including contact information and their timetable. + */ @Route(path = "/teacher_profile", fxml = "/teacher_profile.fxml") public class TeacherProfileController extends DashboardBaseController { @@ -44,6 +48,12 @@ public class TeacherProfileController extends DashboardBaseController { private String teacherTag; + /** + * Initializes the controller when navigating to this view. + * Sets the teacher tag from navigation properties and initiates data loading. + * + * @param props Navigation properties, must contain a "tag" key with the teacher's tag. + */ @Override public void onNavigate(Map props) { super.onNavigate(props); @@ -57,6 +67,10 @@ public class TeacherProfileController extends DashboardBaseController { loadTeacher(); } + /** + * Fetches the teacher profile from the API and updates the UI. + * Shows a loading indicator while fetching. + */ private void loadTeacher() { loadingIndicator.setVisible(true); scrollPane.setVisible(false); @@ -85,6 +99,11 @@ public class TeacherProfileController extends DashboardBaseController { }); } + /** + * Renders the teacher profile information into the UI. + * + * @param teacher The teacher object to display. + */ private void renderTeacher(Teacher teacher) { headerNameLabel.setText(teacher.getFullName()); fullNameLabel.setText(teacher.getFullName()); @@ -127,6 +146,14 @@ public class TeacherProfileController extends DashboardBaseController { } } + /** + * Adds a row of detail information to the details grid. + * + * @param labelText The label text for the detail. + * @param value The value of the detail. + * @param row The row index in the grid. + * @return The next row index. + */ private int addDetailRow(String labelText, String value, int row) { if (value == null || value.isBlank()) { return row; @@ -145,6 +172,9 @@ public class TeacherProfileController extends DashboardBaseController { return row + 1; } + /** + * Handles navigation back to the teachers list view. + */ @FXML @Override protected void onBack() { diff --git a/src/main/java/cz/jzitnik/controllers/TeachersController.java b/src/main/java/cz/jzitnik/controllers/TeachersController.java index c07c5c1..a1980f9 100644 --- a/src/main/java/cz/jzitnik/controllers/TeachersController.java +++ b/src/main/java/cz/jzitnik/controllers/TeachersController.java @@ -15,6 +15,10 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +/** + * Controller for handling the teachers list view. + * Provides functionality to display, search, and navigate to teacher profiles. + */ @Route(path = "/teachers", fxml = "/teachers.fxml") public class TeachersController extends DashboardBaseController { @@ -32,6 +36,12 @@ public class TeachersController extends DashboardBaseController { private List allTeachers = new ArrayList<>(); + /** + * Initializes the controller when navigating to this view. + * Sets up the search filter and initiates data loading. + * + * @param props Navigation properties, not explicitly used here. + */ @Override public void onNavigate(Map props) { super.onNavigate(props); @@ -43,6 +53,10 @@ public class TeachersController extends DashboardBaseController { loadTeachers(); } + /** + * Fetches the list of teachers from the API and updates the UI. + * Shows a loading indicator while fetching. + */ private void loadTeachers() { loadingIndicator.setVisible(true); scrollPane.setVisible(false); @@ -76,6 +90,11 @@ public class TeachersController extends DashboardBaseController { }); } + /** + * Filters the list of teachers based on the provided search query. + * + * @param query The search string to filter by. + */ private void filterTeachers(String query) { teachersFlow.getChildren().clear(); @@ -113,6 +132,9 @@ public class TeachersController extends DashboardBaseController { } } + /** + * Handles navigation back to the dashboard. + */ @FXML protected void onBackToDashboard() { Router.getInstance().navigate("/dashboard"); diff --git a/src/main/java/cz/jzitnik/controllers/TimetableController.java b/src/main/java/cz/jzitnik/controllers/TimetableController.java index 2e50eb8..30a2659 100644 --- a/src/main/java/cz/jzitnik/controllers/TimetableController.java +++ b/src/main/java/cz/jzitnik/controllers/TimetableController.java @@ -21,6 +21,10 @@ import kotlinx.datetime.DayOfWeek; import java.util.List; import java.util.Map; +/** + * Controller for handling the timetable view. + * Displays the weekly timetable for the student. + */ @Route(path = "/timetable", fxml = "/timetable.fxml") public class TimetableController extends DashboardBaseController { @@ -35,12 +39,22 @@ public class TimetableController extends DashboardBaseController { private TimetablePage currentPage; + /** + * Initializes the controller when navigating to this view. + * Initiates loading of timetable data. + * + * @param props Navigation properties, not explicitly used here. + */ @Override public void onNavigate(Map props) { super.onNavigate(props); loadTimetable(); } + /** + * Fetches the timetable page from the API and updates the UI. + * Shows a loading indicator while fetching. + */ private void loadTimetable() { loadingIndicator.setVisible(true); scrollPane.setVisible(false); @@ -56,6 +70,11 @@ public class TimetableController extends DashboardBaseController { }, QueryOptions.defaultOptions()).thenAccept(this::handleTimetableResult); } + /** + * Handles the result of the timetable fetch operation. + * + * @param result The result of the fetch. + */ private void handleTimetableResult(QueryResult result) { Platform.runLater(() -> { loadingIndicator.setVisible(false); @@ -75,6 +94,9 @@ public class TimetableController extends DashboardBaseController { }); } + /** + * Renders the timetable grid using the TimetableRenderer. + */ private void renderTimetable() { timetableGrid.getChildren().clear(); timetableGrid.getColumnConstraints().clear(); @@ -85,6 +107,9 @@ public class TimetableController extends DashboardBaseController { TimetableRenderer.renderTimetable(timetableGrid, timetable); } + /** + * Handles navigation back to the dashboard. + */ @FXML protected void onBackToDashboard() { Router.getInstance().navigate("/dashboard"); diff --git a/src/main/java/cz/jzitnik/query/QueryClient.java b/src/main/java/cz/jzitnik/query/QueryClient.java index 366acfa..4845564 100644 --- a/src/main/java/cz/jzitnik/query/QueryClient.java +++ b/src/main/java/cz/jzitnik/query/QueryClient.java @@ -4,11 +4,23 @@ import java.util.Map; import java.util.concurrent.*; import java.util.function.Supplier; +/** + * A client for handling data fetching with caching and background updates. + */ public class QueryClient { private final Map> cache = new ConcurrentHashMap<>(); private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + /** + * Fetches data with caching support. + * + * @param key The cache key. + * @param fetcher Supplier for fetching data if not cached or stale. + * @param opts Query options for caching behavior. + * @param The type of data. + * @return A CompletableFuture with the result. + */ public CompletableFuture> fetch(String key, Supplier fetcher, QueryOptions opts) { @SuppressWarnings("unchecked") CachedItem item = (CachedItem) cache.get(key); long now = System.currentTimeMillis(); @@ -53,14 +65,29 @@ public class QueryClient { return future; } + /** + * Manually sets an item in the cache. + * + * @param key The cache key. + * @param value The value to cache. + * @param The type of data. + */ public void set(String key, T value) { cache.put(key, new CachedItem<>(value, System.currentTimeMillis())); } + /** + * Invalidates a cache item. + * + * @param key The cache key to remove. + */ public void invalidate(String key) { cache.remove(key); } + /** + * Shuts down the scheduler. + */ public void shutdown() { scheduler.shutdownNow(); } diff --git a/src/main/java/cz/jzitnik/query/QueryOptions.java b/src/main/java/cz/jzitnik/query/QueryOptions.java index b2eacc0..1206225 100644 --- a/src/main/java/cz/jzitnik/query/QueryOptions.java +++ b/src/main/java/cz/jzitnik/query/QueryOptions.java @@ -1,17 +1,28 @@ package cz.jzitnik.query; +/** + * Configuration options for query operations, such as caching and retries. + */ public class QueryOptions { - // time until data is considered stale (ms) - public long staleTime = 5 * 60 * 1000; // 5 minutes - // time until unused cache entry is evicted (ms) - public long cacheTime = 30 * 60 * 1000; // 30 minutes - // if >0, interval to refetch in background (ms) + /** Time until data is considered stale (ms). Default: 5 minutes. */ + public long staleTime = 5 * 60 * 1000; + /** Time until unused cache entry is evicted (ms). Default: 30 minutes. */ + public long cacheTime = 30 * 60 * 1000; + /** If > 0, interval to refetch in background (ms). Default: 0 (disabled). */ public long refetchInterval = 0; - // number of retry attempts on failure + /** Number of retry attempts on failure. Default: 0. */ public int retryAttempts = 0; + /** + * Creates new default query options. + */ public QueryOptions() {} + /** + * Returns default query options. + * + * @return Default QueryOptions instance. + */ public static QueryOptions defaultOptions() { return new QueryOptions(); } diff --git a/src/main/java/cz/jzitnik/query/QueryResult.java b/src/main/java/cz/jzitnik/query/QueryResult.java index 6754113..2a3e2e4 100644 --- a/src/main/java/cz/jzitnik/query/QueryResult.java +++ b/src/main/java/cz/jzitnik/query/QueryResult.java @@ -3,19 +3,53 @@ package cz.jzitnik.query; import java.time.Instant; import java.util.Optional; +/** + * Represents the result of a query operation. + * + * @param The type of the data. + */ public class QueryResult { private final T data; private final Throwable error; private final Instant fetchedAt; + /** + * Creates a new QueryResult. + * + * @param data The query data, or null if an error occurred. + * @param error The error, or null if successful. + */ public QueryResult(T data, Throwable error) { this.data = data; this.error = error; this.fetchedAt = Instant.now(); } + /** + * Gets the data. + * + * @return Optional containing the data. + */ public Optional getData() { return Optional.ofNullable(data); } + + /** + * Gets the error. + * + * @return Optional containing the error. + */ public Optional getError() { return Optional.ofNullable(error); } + + /** + * Gets the fetch time. + * + * @return The fetch time. + */ public Instant getFetchedAt() { return fetchedAt; } + + /** + * Checks if the query was successful. + * + * @return True if successful, false otherwise. + */ public boolean isSuccess() { return error == null; } } diff --git a/src/main/java/cz/jzitnik/router/Router.java b/src/main/java/cz/jzitnik/router/Router.java index 3c6c4aa..b7df5bb 100644 --- a/src/main/java/cz/jzitnik/router/Router.java +++ b/src/main/java/cz/jzitnik/router/Router.java @@ -11,6 +11,9 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; +/** + * A simple router for navigating between views in a JavaFX application. + */ public class Router { private static Router instance; private Pane rootContainer; @@ -18,6 +21,11 @@ public class Router { private Router() {} + /** + * Gets the singleton instance of the router. + * + * @return The Router instance. + */ public static Router getInstance() { if (instance == null) { instance = new Router(); @@ -25,10 +33,20 @@ public class Router { return instance; } + /** + * Sets the root container pane for the router. + * + * @param rootContainer The root container. + */ public void setRootContainer(Pane rootContainer) { this.rootContainer = rootContainer; } + /** + * Registers routes annotated with @Route in the specified base package. + * + * @param basePackage The base package to scan for routes. + */ public void registerAnnotatedRoutes(String basePackage) { Reflections r = new Reflections(basePackage); Set> controllers = r.getTypesAnnotatedWith(Route.class); @@ -40,10 +58,21 @@ public class Router { } } + /** + * Navigates to a specific path. + * + * @param path The path to navigate to. + */ public void navigate(String path) { navigate(path, new HashMap<>()); } + /** + * Navigates to a specific path with navigation properties. + * + * @param path The path to navigate to. + * @param props Navigation properties. + */ public void navigate(String path, Map props) { if (rootContainer == null) { throw new IllegalStateException("Root container not set in Router."); diff --git a/src/main/java/cz/jzitnik/state/AppState.java b/src/main/java/cz/jzitnik/state/AppState.java index 69a26d4..a0cf1ca 100644 --- a/src/main/java/cz/jzitnik/state/AppState.java +++ b/src/main/java/cz/jzitnik/state/AppState.java @@ -4,6 +4,11 @@ import cz.jzitnik.auth.CredentialStore; import io.github.tomhula.jecnaapi.java.JecnaClientJavaWrapper; import cz.jzitnik.query.QueryClient; +/** + * Manages the current application state, including user authentication and API interactions. + * This class serves as the central hub for tracking the logged-in user, managing + * the active Jecna API client session, and providing access to query tools. + */ @State public class AppState { @@ -12,26 +17,59 @@ public class AppState { private JecnaClientJavaWrapper client; private QueryClient queryClient; + /** + * Gets the username of the currently logged-in user. + * + * @return the username, or null if no user is logged in. + */ public String getUsername() { return username; } + /** + * Gets the password of the currently logged-in user. + * + * @return the password, or null if no user is logged in. + */ public String getPassword() { return password; } + /** + * Gets the active Jecna API client wrapper. + * + * @return the Jecna API client instance, or null if not initialized/logged in. + */ public JecnaClientJavaWrapper getClient() { return client; } + /** + * Gets the query client used for fetching specific data. + * + * @return the QueryClient instance. + */ public QueryClient getQueryClient() { return queryClient; } + /** + * Sets the query client for this state. + * + * @param qc the QueryClient instance to use. + */ public void setQueryClient(QueryClient qc) { this.queryClient = qc; } + /** + * Attempts to log the user into the Jecna API with the provided credentials. + * If successful, it stores the credentials securely for future sessions. + * + * @param username the user's username. + * @param password the user's password. + * @return true if login was successful, false otherwise. + */ public boolean login(String username, String password) { if (username == null || username.isEmpty() || password == null) return false; @@ -61,6 +99,10 @@ public class AppState { } } + /** + * Clears the current application state, including logging out the user + * and resetting the API client. + */ public void clear() { this.username = null; this.password = null; diff --git a/src/main/java/cz/jzitnik/ui/AuroraBackground.java b/src/main/java/cz/jzitnik/ui/AuroraBackground.java index 0bee80e..041a19f 100644 --- a/src/main/java/cz/jzitnik/ui/AuroraBackground.java +++ b/src/main/java/cz/jzitnik/ui/AuroraBackground.java @@ -11,8 +11,14 @@ import javafx.scene.paint.Color; import javafx.scene.shape.Circle; import javafx.util.Duration; +/** + * A UI component that displays an animated aurora-like background. + */ public class AuroraBackground extends StackPane { + /** + * Initializes the aurora background with animated blobs of color. + */ public AuroraBackground() { Color color1 = Color.web("#00FFFF", 0.10); // Cyan Color color2 = Color.web("#FF00FF", 0.10); // Magenta @@ -38,6 +44,16 @@ public class AuroraBackground extends StackPane { setupAnimation(blob3, 0, -200, 0, 200, 15); } + /** + * Sets up the animation for a color blob. + * + * @param node The circle node to animate. + * @param startX Starting X translation. + * @param startY Starting Y translation. + * @param endX Ending X translation. + * @param endY Ending Y translation. + * @param durationSeconds Duration of the animation in seconds. + */ private void setupAnimation(Circle node, double startX, double startY, double endX, double endY, int durationSeconds) { Timeline timeline = new Timeline( new KeyFrame(Duration.ZERO, diff --git a/src/main/java/cz/jzitnik/util/ImageFetcher.java b/src/main/java/cz/jzitnik/util/ImageFetcher.java index d1d794b..403798c 100644 --- a/src/main/java/cz/jzitnik/util/ImageFetcher.java +++ b/src/main/java/cz/jzitnik/util/ImageFetcher.java @@ -8,8 +8,20 @@ import java.net.URL; import java.util.zip.GZIPInputStream; import java.util.zip.InflaterInputStream; +/** + * A little utility to grab images from the school website. + * Since the server can be picky about requests, we have to pretend + * to be a real browser to get the files successfully. + */ public class ImageFetcher { + /** + * Fetches an image from the school server, handling decompression + * and proper headers to avoid being blocked. + * + * @param imagePath The relative path to the image on the school server. + * @return The loaded JavaFX Image, or null if something went wrong. + */ public static Image fetchImage(String imagePath) { if (imagePath == null || imagePath.isEmpty()) { return null; diff --git a/src/main/java/cz/jzitnik/util/ScrollUtils.java b/src/main/java/cz/jzitnik/util/ScrollUtils.java index 5522b5b..c2947a8 100644 --- a/src/main/java/cz/jzitnik/util/ScrollUtils.java +++ b/src/main/java/cz/jzitnik/util/ScrollUtils.java @@ -7,8 +7,18 @@ import javafx.scene.control.ScrollPane; import javafx.scene.input.ScrollEvent; import javafx.util.Duration; +/** + * Helps make scrolling through the app feel a bit more premium. + * Instead of jumping straight to the new position, this gently animates + * the scroll, making it much easier on the eyes. + */ public class ScrollUtils { + /** + * Enables smooth, animated scrolling for a given ScrollPane. + * + * @param scrollPane The ScrollPane we want to make feel smoother. + */ public static void enableSmoothScrolling(ScrollPane scrollPane) { scrollPane.addEventFilter(ScrollEvent.SCROLL, event -> { double deltaY = event.getDeltaY(); diff --git a/src/main/java/cz/jzitnik/util/TimetableRenderer.java b/src/main/java/cz/jzitnik/util/TimetableRenderer.java index 1bb16d6..d9fe578 100644 --- a/src/main/java/cz/jzitnik/util/TimetableRenderer.java +++ b/src/main/java/cz/jzitnik/util/TimetableRenderer.java @@ -21,8 +21,17 @@ import kotlinx.datetime.DayOfWeek; import java.util.List; import java.util.Map; +/** + * Utility class for rendering the timetable in a JavaFX GridPane. + */ public class TimetableRenderer { + /** + * Renders the timetable into the provided GridPane. + * + * @param timetableGrid The GridPane to render into. + * @param timetable The timetable data to render. + */ public static void renderTimetable(GridPane timetableGrid, Timetable timetable) { timetableGrid.getChildren().clear(); timetableGrid.getColumnConstraints().clear(); @@ -92,6 +101,12 @@ public class TimetableRenderer { } } + /** + * Creates a cell UI component for a lesson spot. + * + * @param spot The lesson spot data. + * @return The created VBox component. + */ private static VBox createLessonCell(LessonSpot spot) { VBox cell = new VBox(5); cell.setAlignment(Pos.CENTER); @@ -131,6 +146,12 @@ public class TimetableRenderer { return cell; } + /** + * Adds lesson information to a cell UI component. + * + * @param cell The cell UI component. + * @param lesson The lesson data. + */ private static void addLessonInfoToCell(VBox cell, Lesson lesson) { Label subjectLbl = new Label(lesson.getSubjectName().getShort() != null ? lesson.getSubjectName().getShort() : lesson.getSubjectName().getFull()); subjectLbl.getStyleClass().add("timetable-subject"); @@ -153,6 +174,11 @@ public class TimetableRenderer { cell.getChildren().addAll(subjectLbl, bottomInfo); } + /** + * Shows a dialog with detailed lesson information. + * + * @param spot The lesson spot data. + */ private static void showLessonDetails(LessonSpot spot) { Dialog dialog = new Dialog<>(); dialog.setTitle("Detail hodiny"); @@ -209,6 +235,12 @@ public class TimetableRenderer { dialog.show(); } + /** + * Translates DayOfWeek to a Czech string. + * + * @param day The DayOfWeek. + * @return The translated day name. + */ private static String translateDay(DayOfWeek day) { return switch (day) { case MONDAY -> "Pondělí";