/
aleksandrazimut
/
TimerCountdownDesktop
Обзор
Документация
Войти
/
aleksandrazimut
/
TimerCountdownDesktop
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/main/java/com/timerapp/controller/AlarmController.java
792 строки
35 KB
Aleksandr Azimut
init commit
17 дек 2025, 21:59
17 дек 2025, 21:59
3c602f1
Код
Авторство
О чём код?
package com.timerapp.controller; import com.timerapp.model.ThemeColors; import com.timerapp.service.AudioService; import javafx.animation.KeyFrame; import javafx.animation.KeyValue; import javafx.animation.Timeline; import javafx.fxml.FXML; import javafx.scene.control.Button; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.*; import javafx.scene.shape.Arc; import javafx.scene.shape.Circle; import javafx.stage.Stage; import javafx.util.Duration; import java.io.File; /** * Контроллер окна оповещения при завершении таймера */ public class AlarmController { @FXML private VBox rootPane; // Корневая панель (изменено с BorderPane на VBox) @FXML private Button closeButton; // Кнопка "Закрыть" @FXML private Button restartButton; // Кнопка "Повторить" @FXML private ImageView iconImageView; // Иконка темы @FXML private Circle progressTrackCircle; // Фоновый круг @FXML private Arc progressArc; // Анимированная дуга // Рейтинг @FXML private HBox rateUsBoxAlarm; @FXML private ImageView starAlarm1, starAlarm2, starAlarm3, starAlarm4, starAlarm5; private String backgroundPath; private Runnable onRestartCallback; // Callback для повтора таймера private Runnable onCloseCallback; // Callback для закрытия окна private Timeline animation; // Анимация круга private AudioService audioService; // Сервис для управления звуком // Картинки для звездочек private Image starRegular; private Image starSolid; private boolean ratingSet = false; // Флаг: был ли установлен рейтинг private int savedRating = 0; // Сохраненный рейтинг @FXML public void initialize() { // Улучшает качество рендеринга изображений System.setProperty("prism.order", "sw"); System.setProperty("prism.lcdtext", "false"); // Применяет фон, если он есть if (backgroundPath != null && !backgroundPath.isEmpty()) { applyBackground(); } // Оптимизация рендеринга Arc для плавной анимации if (progressArc != null) { // Убрал кэширование - оно может вызывать тряску progressArc.setSmooth(true); } // Инициализирует звездочки initializeStars(); } /** * Инициализирует звездочки для рейтинга */ private void initializeStars() { try { // Загружает картинки звездочек (GREEN) starRegular = new Image(getClass().getResourceAsStream("/img/star_regular_green.png")); starSolid = new Image(getClass().getResourceAsStream("/img/star_solid_green.png")); // Устанавливает пустые звездочки по умолчанию if (starAlarm1 != null) starAlarm1.setImage(starRegular); if (starAlarm2 != null) starAlarm2.setImage(starRegular); if (starAlarm3 != null) starAlarm3.setImage(starRegular); if (starAlarm4 != null) starAlarm4.setImage(starRegular); if (starAlarm5 != null) starAlarm5.setImage(starRegular); System.out.println("[AlarmController] Звездочки инициализированы"); } catch (Exception e) { System.err.println("[AlarmController] Ошибка инициализации звездочек: " + e.getMessage()); e.printStackTrace(); } } /** * Запускает анимацию кругового прогресса * Синхронизируется с длительностью звука */ private void startProgressAnimation() { System.out.println("[AlarmController] startProgressAnimation вызван"); if (progressArc == null) { System.err.println("[AlarmController] progressArc is null"); return; } if (audioService == null) { System.err.println("[AlarmController] audioService null, используем fallback анимацию 3 сек"); startFallbackAnimation(); return; } var mediaPlayer = audioService.getMediaPlayer(); if (mediaPlayer == null) { System.err.println( "[AlarmController] MediaPlayer null (возможно OGG файл), используем fallback анимацию 3 сек"); startFallbackAnimation(); return; } System.out.println("[AlarmController] MediaPlayer найден, статус: " + mediaPlayer.getStatus()); // Проверяет статус MediaPlayer if (mediaPlayer.getStatus() == javafx.scene.media.MediaPlayer.Status.READY) { // MediaPlayer уже готов, сразу устанавливаем слушатель System.out.println("[AlarmController] MediaPlayer уже READY, устанавливаем слушатель"); setupProgressListener(mediaPlayer); } else { // Ждет пока MediaPlayer загрузит метаданные System.out.println("[AlarmController] Ждем READY события от MediaPlayer"); mediaPlayer.setOnReady(() -> { System.out.println("[AlarmController] MediaPlayer READY событие получено"); setupProgressListener(mediaPlayer); }); } } /** * Устанавливает слушатель прогресса для синхронизации анимации со звуком */ private void setupProgressListener(javafx.scene.media.MediaPlayer mediaPlayer) { javafx.util.Duration soundDuration = mediaPlayer.getTotalDuration(); System.out.println("[AlarmController] Длительность звука: " + soundDuration.toSeconds() + " сек"); if (soundDuration.isUnknown() || soundDuration.lessThanOrEqualTo(javafx.util.Duration.ZERO)) { System.err.println("[AlarmController] Длительность звука неизвестна, используем fallback"); startFallbackAnimation(); return; } // Использует ScheduledExecutorService для регулярного обновления final java.util.concurrent.ScheduledExecutorService scheduler = java.util.concurrent.Executors .newScheduledThreadPool(1); scheduler.scheduleAtFixedRate(() -> { javafx.util.Duration currentTime = mediaPlayer.getCurrentTime(); double progress = currentTime.toMillis() / soundDuration.toMillis(); if (progress > 1.0) progress = 1.0; if (progress < 0.0) progress = 0.0; final double finalProgress = progress; // Обновляет UI в JavaFX потоке javafx.application.Platform.runLater(() -> { // Если близко к концу (98%), сразу ставим полный круг if (finalProgress >= 0.98) { progressArc.setLength(-360); } else { progressArc.setLength(-360 * finalProgress); } }); // Останавливает когда близко к концу или звук остановлен if (progress >= 0.98 || mediaPlayer.getStatus() == javafx.scene.media.MediaPlayer.Status.STOPPED) { scheduler.shutdown(); } }, 0, 500, java.util.concurrent.TimeUnit.MILLISECONDS); System.out.println("[AlarmController] Анимация синхронизирована с звуком (Scheduler 500ms)"); } /** * Fallback анимация на случай если MediaPlayer недоступен */ private void startFallbackAnimation() { animation = new Timeline( new KeyFrame(Duration.ZERO, new KeyValue(progressArc.lengthProperty(), 0)), new KeyFrame(Duration.seconds(3), new KeyValue(progressArc.lengthProperty(), -360))); animation.setCycleCount(1); animation.play(); System.out.println("[AlarmController] Fallback анимация (3 сек) запущена"); } /** * Устанавливает путь к фоновому изображению */ public void setBackgroundPath(String backgroundPath) { System.out.println("[AlarmController] setBackgroundPath вызван с: " + backgroundPath); this.backgroundPath = backgroundPath; applyBackground(); } /** * Устанавливает иконку темы */ public void setIconPath(String iconPath) { if (iconImageView != null && iconPath != null && !iconPath.isEmpty()) { try { Image icon = null; // Загружает PNG/JPG/WEBP иконки File file = new File(iconPath); if (file.exists()) { icon = new Image(file.toURI().toString()); System.out.println("[AlarmController] Загружена пользовательская иконка из файла: " + iconPath); } else { // Пробует загрузить из ресурсов // Если путь не начинается с '/', добавляем его для корректной загрузки ресурсов String resourcePath = iconPath.startsWith("/") ? iconPath : "/" + iconPath; icon = new Image(getClass().getResourceAsStream(resourcePath)); System.out.println("[AlarmController] Загружена иконка из ресурса: " + resourcePath); } if (icon != null) { iconImageView.setImage(icon); iconImageView.setVisible(true); System.out.println("[AlarmController] Иконка установлена успешно"); } } catch (Exception e) { System.err.println("[AlarmController] Ошибка загрузки иконки: " + e.getMessage()); e.printStackTrace(); } } } /** * Применяет фоновое изображение */ private void applyBackground() { if (backgroundPath != null && !backgroundPath.isEmpty() && rootPane != null) { try { // Получает URL фонового изображения java.net.URL url = null; // Сначала пробуем как файл File file = new File(backgroundPath); if (file.exists()) { url = file.toURI().toURL(); } else { // Пробует как ресурс url = getClass().getResource(backgroundPath); } if (url != null) { // Применяет через CSS стили с !important (как в главном окне) // Добавляет -fx-smooth: false для отключения сглаживания и повышения четкости String bgStyle = String.format( "-fx-background-image: url(\"%s\") !important; -fx-background-size: cover !important; -fx-background-position: center !important;", url.toExternalForm()); rootPane.setStyle(bgStyle); System.out.println("[AlarmController] Фон установлен через CSS стили: " + url.toExternalForm()); } else { System.err.println("[AlarmController] Фон не найден (ни файл, ни ресурс): " + backgroundPath); } } catch (Exception e) { System.err.println("[AlarmController] Ошибка применения фона: " + e.getMessage()); e.printStackTrace(); } } else { System.out.println("[AlarmController] backgroundPath или rootPane null"); } } /** * Устанавливает callback для повтора таймера */ public void setOnRestart(Runnable callback) { this.onRestartCallback = callback; } /** * Устанавливает callback для закрытия окна (сброс таймера) */ public void setOnClose(Runnable callback) { this.onCloseCallback = callback; } /** * Устанавливает сервис для управления звуком */ public void setAudioService(AudioService audioService) { this.audioService = audioService; // НЕ запускает анимацию здесь - она будет запущена после playSound() из // MainController } /** * Запускает анимацию (вызывается из MainController после playSound) */ public void startAnimation() { startProgressAnimation(); } /** * Применяет цветовую схему темы к кнопкам */ public void applyThemeColors(ThemeColors colors, boolean isSimpleTheme, boolean isFalloutTheme, boolean isLondonTheme, boolean isPokemonTheme, boolean isVaderTheme, boolean isR2D2Theme) { if (colors == null) { return; } System.out.println("[AlarmController] applyThemeColors: isSimple=" + isSimpleTheme + ", isFallout=" + isFalloutTheme + ", isLondon=" + isLondonTheme + ", isPokemon=" + isPokemonTheme + ", isVader=" + isVaderTheme + ", isR2D2=" + isR2D2Theme); // Стиль для кнопки Close String closeButtonStyle; if (isSimpleTheme) { // Simple theme: синий цвет для Close closeButtonStyle = String.format( "-fx-background-color: #013d99; -fx-text-fill: white; " + "-fx-font-size: 19.2px; -fx-min-width: 130px; " + "-fx-padding: 0; -fx-pref-height: 48px; " + "-fx-background-radius: 12.8; " + "-fx-border-color: transparent; -fx-border-width: 1; -fx-border-radius: 12.8; " + "-fx-cursor: hand;"); } else if (isFalloutTheme) { // Fallout theme: зеленый цвет для Close closeButtonStyle = String.format( "-fx-background-color: #0e980c; -fx-text-fill: white; " + "-fx-font-size: 19.2px; -fx-min-width: 130px; " + "-fx-padding: 0; -fx-pref-height: 48px; " + "-fx-background-radius: 12.8; " + "-fx-border-color: transparent; -fx-border-width: 1; -fx-border-radius: 12.8; " + "-fx-cursor: hand;"); } else if (isLondonTheme) { // London theme: красный цвет для Close closeButtonStyle = String.format( "-fx-background-color: #ba0001; -fx-text-fill: white; " + "-fx-font-size: 19.2px; -fx-min-width: 130px; " + "-fx-padding: 0; -fx-pref-height: 48px; " + "-fx-background-radius: 12.8; " + "-fx-border-color: transparent; -fx-border-width: 1; -fx-border-radius: 12.8; " + "-fx-cursor: hand;"); } else if (isPokemonTheme) { // Pokemon theme: темно-красный цвет для Close closeButtonStyle = String.format( "-fx-background-color: #990001; -fx-text-fill: white; " + "-fx-font-size: 19.2px; -fx-min-width: 130px; " + "-fx-padding: 0; -fx-pref-height: 48px; " + "-fx-background-radius: 12.8; " + "-fx-border-color: transparent; -fx-border-width: 1; -fx-border-radius: 12.8; " + "-fx-cursor: hand;"); } else if (isVaderTheme) { // Vader theme: голубой цвет для Close closeButtonStyle = String.format( "-fx-background-color: #00869a; -fx-text-fill: white; " + "-fx-font-size: 19.2px; -fx-min-width: 130px; " + "-fx-padding: 0; -fx-pref-height: 48px; " + "-fx-background-radius: 12.8; " + "-fx-border-color: transparent; -fx-border-width: 1; -fx-border-radius: 12.8; " + "-fx-cursor: hand;"); } else if (isR2D2Theme) { // R2D2 theme: фиолетовый цвет для Close closeButtonStyle = String.format( "-fx-background-color: #1f0099; -fx-text-fill: white; " + "-fx-font-size: 19.2px; -fx-min-width: 130px; " + "-fx-padding: 0; -fx-pref-height: 48px; " + "-fx-background-radius: 12.8; " + "-fx-border-color: transparent; -fx-border-width: 1; -fx-border-radius: 12.8; " + "-fx-cursor: hand;"); } else { // Другие темы: использует цвет темы closeButtonStyle = String.format( "-fx-background-color: %s; -fx-text-fill: %s; " + "-fx-font-size: 19.2px; -fx-min-width: 130px; " + "-fx-padding: 0; -fx-pref-height: 48px; " + "-fx-background-radius: 12.8; " + "-fx-border-color: transparent; -fx-border-width: 1; -fx-border-radius: 12.8; " + "-fx-cursor: hand;", colors.getPresetsBtnBg(), colors.getPresetsBtnText()); } // Стиль для кнопки Restart String restartButtonStyle; // Для всех тем : серый цвет для Restart if (isSimpleTheme || isFalloutTheme || isLondonTheme || isPokemonTheme || isVaderTheme || isR2D2Theme) { restartButtonStyle = String.format( "-fx-background-color: #596772; -fx-text-fill: white; " + "-fx-font-size: 19.2px; -fx-min-width: 130px; " + "-fx-padding: 0; -fx-pref-height: 48px; " + "-fx-background-radius: 12.8; " + "-fx-border-color: transparent; -fx-border-width: 1; -fx-border-radius: 12.8; " + "-fx-cursor: hand;"); } else { // Другие темы: используем цвет темы restartButtonStyle = String.format( "-fx-background-color: %s; -fx-text-fill: %s; " + "-fx-font-size: 19.2px; -fx-min-width: 130px; " + "-fx-padding: 0; -fx-pref-height: 48px; " + "-fx-background-radius: 12.8; " + "-fx-border-color: transparent; -fx-border-width: 1; -fx-border-radius: 12.8; " + "-fx-cursor: hand;", colors.getPresetsBtnBg(), colors.getPresetsBtnText()); } // Применяет стили closeButton.setStyle(closeButtonStyle); restartButton.setStyle(restartButtonStyle); // Добавляет hover эффекты addHoverEffect(closeButton, closeButtonStyle); addHoverEffect(restartButton, restartButtonStyle); } /** * Добавляет hover и pressed эффекты к кнопке */ private void addHoverEffect(Button button, String baseStyle) { button.setOnMouseEntered(e -> { // При наведении: opacity 1, border-color более заметная button.setStyle(baseStyle + " -fx-opacity: 1; -fx-border-color: rgba(255,255,255,0.5);"); }); button.setOnMouseExited(e -> { button.setStyle(baseStyle); }); button.setOnMousePressed(e -> { button.setStyle(baseStyle); }); button.setOnMouseReleased(e -> { // Возвращаемся к hover состоянию если мышь все еще над кнопкой if (button.isHover()) { button.setStyle(baseStyle + " -fx-opacity: 1; -fx-border-color: rgba(255,255,255,0.5);"); } else { button.setStyle(baseStyle); } }); } /** * Обработчик кнопки "Закрыть" */ @FXML private void onClose() { if (animation != null) animation.stop(); if (audioService != null) audioService.stop(); // Вызывает callback для сброса таймера if (onCloseCallback != null) { onCloseCallback.run(); } Stage stage = (Stage) closeButton.getScene().getWindow(); stage.close(); } /** * Обработчик кнопки "Repeat" - перезапускает таймер */ @FXML private void onRestart() { if (animation != null) animation.stop(); if (audioService != null) audioService.stop(); // Вызывает callback для перезапуска таймера if (onRestartCallback != null) { onRestartCallback.run(); } // Закрывае окно alarm Stage stage = (Stage) restartButton.getScene().getWindow(); stage.close(); } /** * Применяет стили кнопок (градиент/цвет/прозрачность + текст) */ public void setButtonStyles(String gradient, String color, double opacity, String textColor) { String backgroundStyle = ""; // Определяет градиент или однотонный цвет if (gradient != null && !gradient.equals("None")) { backgroundStyle = getGradientStyle(gradient); // Убирает !important из конца, так как добавляем его в конце общей строки backgroundStyle = backgroundStyle.replace(" !important;", ""); } else if (color != null) { backgroundStyle = "-fx-background-color: " + color; } else { backgroundStyle = "-fx-background-color: #4a5568"; // fallback } if (textColor == null || textColor.isEmpty()) { textColor = "#FFFFFF"; } // Собирает полный стиль, идентичный applyThemeColors String fullStyle = String.format( "%s; -fx-text-fill: %s; -fx-opacity: %s; " + "-fx-font-family: 'Century Gothic Paneuropean'; -fx-font-size: 14px; -fx-font-weight: bold; " + "-fx-padding: 12 24 12 24; -fx-background-radius: 8; " + "-fx-cursor: hand; -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.4), 6, 0.4, 0, 3) !important;", backgroundStyle, textColor, opacity); // Применяет к кнопкам if (restartButton != null) { restartButton.setStyle(fullStyle); // Добавляет hover эффекты addHoverEffect(restartButton, fullStyle.replace(" !important;", "")); } if (closeButton != null) { closeButton.setStyle(fullStyle); // Добавляет hover эффекты addHoverEffect(closeButton, fullStyle.replace(" !important;", "")); } } /** * Возвращает CSS градиент по названию */ private String getGradientStyle(String gradient) { switch (gradient) { case "Blue": return "-fx-background-color: linear-gradient(to bottom, #4299e1, #2b6cb0) !important;"; case "Red": return "-fx-background-color: linear-gradient(to bottom, #f56565, #c53030) !important;"; case "Green": return "-fx-background-color: linear-gradient(to bottom, #48bb78, #2f855a) !important;"; case "Purple": return "-fx-background-color: linear-gradient(to bottom, #9f7aea, #6b46c1) !important;"; case "Orange": return "-fx-background-color: linear-gradient(to bottom, #ed8936, #c05621) !important;"; default: return "-fx-background-color: #4a5568 !important;"; } } /** * Применяет пользовательские стили к кнопкам (градиент, прозрачность) */ public void applyButtonStyles(com.timerapp.model.TimerConfig config) { if (config == null) return; if (closeButton == null || restartButton == null) return; String gradient = config.getButtonGradient(); String color = config.getButtonColor(); double opacity = config.getButtonOpacity(); String textColor = config.getButtonTextColor(); System.out.println("[AlarmController] Применение стилей кнопок: gradient=" + gradient + ", color=" + color + ", opacity=" + opacity + ", textColor=" + textColor); // Проверка: есть ли пользовательские стили? boolean hasCustomStyle = (gradient != null && !gradient.isEmpty() && !gradient.equalsIgnoreCase("none") && !gradient.equalsIgnoreCase("нет")); // Если цвета отличаются от дефолтных (белый или серый) if (!hasCustomStyle && color != null && !color.isEmpty() && !color.equalsIgnoreCase("#4a5568") && !color.equalsIgnoreCase("#ffffff")) { hasCustomStyle = true; } if (!hasCustomStyle) { System.out.println("[AlarmController] Нет пользовательских стилей, оставляем стили темы"); // НЕ сбрасываем стили - оставляем те что установлены в applyThemeColors return; } // Формирует inline стиль StringBuilder styleBuilder = new StringBuilder(); // Фон: градиент или цвет if (gradient != null && !gradient.isEmpty() && !gradient.equalsIgnoreCase("none") && !gradient.equalsIgnoreCase("нет")) { // Кнвертируем в CSS gradient String cssGradient = convertGradientToCss(gradient); styleBuilder.append("-fx-background-color: ").append(cssGradient).append("; "); } else if (color != null && !color.isEmpty()) { styleBuilder.append("-fx-background-color: ").append(color).append("; "); } // Цвет текста if (textColor != null && !textColor.isEmpty()) { styleBuilder.append("-fx-text-fill: ").append(textColor).append("; "); } // Прозрачность styleBuilder.append("-fx-opacity: ").append(opacity).append("; "); // Base button styles styleBuilder.append("-fx-background-radius: 12.8; "); styleBuilder.append("-fx-font-size: 19.2px; "); styleBuilder.append("-fx-min-width: 130px; "); styleBuilder.append("-fx-padding: 15 20 15 20; "); styleBuilder.append("-fx-cursor: hand;"); String finalStyle = styleBuilder.toString(); // Стиль при наведении (full opacity) // Заменяет прозрачность на 1.0 - используем регулярку для замены существующего // opacity String hoverStyle = finalStyle.replaceAll("-fx-opacity: [0-9.]+", "-fx-opacity: 1.0"); // Применяет стили и слушатели applyStyleWithHover(closeButton, finalStyle, hoverStyle); applyStyleWithHover(restartButton, finalStyle, hoverStyle); } private void applyStyleWithHover(Button btn, String normalStyle, String hoverStyle) { btn.setStyle(normalStyle); btn.setOnMouseEntered(e -> btn.setStyle(hoverStyle)); btn.setOnMouseExited(e -> btn.setStyle(normalStyle)); } /** * Конвертирует внутреннее название градиента в CSS linear-gradient */ private String convertGradientToCss(String gradient) { // Маппинг градиентов switch (gradient) { case "h_light_blue": return "linear-gradient(to bottom, #e0f7fa, #b2ebf2)"; case "h_green": return "linear-gradient(to bottom, #c8e6c9, #a5d6a7)"; case "h_purple": return "linear-gradient(to bottom, #e1bee7, #ce93d8)"; case "h_pink": return "linear-gradient(to bottom, #f8bbd0, #f48fb1)"; case "h_orange": return "linear-gradient(to bottom, #ffe0b2, #ffcc80)"; case "h_beige": return "linear-gradient(to bottom, #d7ccc8, #bcaaa4)"; case "v_blue_indigo": return "linear-gradient(to right, #1976d2, #283593)"; case "v_green_teal": return "linear-gradient(to right, #388e3c, #00796b)"; case "v_red_pink": return "linear-gradient(to right, #d32f2f, #c2185b)"; case "v_purple_deep": return "linear-gradient(to right, #7b1fa2, #4a148c)"; default: return "#4a5568"; } } // ===== РЕЙТИНГ (звездочки) ===== /** * Обработчик наведения мыши на звездочку (превью) */ @FXML public void onStarHover(javafx.scene.input.MouseEvent event) { // Если рейтинг уже установлен - не реагируем на hover if (ratingSet) { return; } ImageView star = (ImageView) event.getSource(); if (star.getUserData() != null) { int rank = Integer.parseInt(star.getUserData().toString()); previewStars(rank); } } /** * Обработчик клика по звездочке */ @FXML public void onStarClick(javafx.scene.input.MouseEvent event) { ImageView star = (ImageView) event.getSource(); if (star.getUserData() != null) { int rank = Integer.parseInt(star.getUserData().toString()); setStarsRating(rank); savedRating = rank; // Отключает интерактивность после первого клика ratingSet = true; disableStarInteraction(); // Простой GET запрос new Thread(() -> { try { java.net.URL url = java.net.URI.create("https://www.example.com").toURL(); java.net.HttpURLConnection con = (java.net.HttpURLConnection) url.openConnection(); con.setRequestMethod("GET"); con.setConnectTimeout(3000); con.setReadTimeout(3000); int status = con.getResponseCode(); System.out.println("[AlarmController] GET request sent. Status: " + status); con.disconnect(); } catch (Exception e) { System.err.println("[AlarmController] GET request failed: " + e.getMessage()); } }).start(); System.out.println("[AlarmController] Рейтинг установлен: " + rank); } } /** * Обработчик выхода мыши из области рейтинга */ @FXML public void onRateMouseExit() { // Если рейтинг НЕ был установлен - сбрасываем все звезды в пустые if (!ratingSet) { setStarsRating(0); // Все звезды пустые } else { // Возвращает сохраненный рейтинг (если был установлен) setStarsRating(savedRating); } } /** * Показывает ПРЕВЬЮ - подсвечивает звезды ВКЛЮЧИТЕЛЬНО */ private void previewStars(int rank) { if (starRegular != null && starSolid != null) { if (starAlarm1 != null) starAlarm1.setImage(rank >= 1 ? starSolid : starRegular); if (starAlarm2 != null) starAlarm2.setImage(rank >= 2 ? starSolid : starRegular); if (starAlarm3 != null) starAlarm3.setImage(rank >= 3 ? starSolid : starRegular); if (starAlarm4 != null) starAlarm4.setImage(rank >= 4 ? starSolid : starRegular); if (starAlarm5 != null) starAlarm5.setImage(rank >= 5 ? starSolid : starRegular); } } /** * Устанавливает рейтинг -подсвечивает звезды ВКЛЮЧИТЕЛЬНО */ private void setStarsRating(int rank) { if (starRegular != null && starSolid != null) { if (starAlarm1 != null) starAlarm1.setImage(rank >= 1 ? starSolid : starRegular); if (starAlarm2 != null) starAlarm2.setImage(rank >= 2 ? starSolid : starRegular); if (starAlarm3 != null) starAlarm3.setImage(rank >= 3 ? starSolid : starRegular); if (starAlarm4 != null) starAlarm4.setImage(rank >= 4 ? starSolid : starRegular); if (starAlarm5 != null) starAlarm5.setImage(rank >= 5 ? starSolid : starRegular); } } /** * Отключает интерактивность звездочек после установки рейтинга */ private void disableStarInteraction() { if (starAlarm1 != null) starAlarm1.setStyle("-fx-cursor: default;"); if (starAlarm2 != null) starAlarm2.setStyle("-fx-cursor: default;"); if (starAlarm3 != null) starAlarm3.setStyle("-fx-cursor: default;"); if (starAlarm4 != null) starAlarm4.setStyle("-fx-cursor: default;"); if (starAlarm5 != null) starAlarm5.setStyle("-fx-cursor: default;"); } }