From 76098a42b2d0431734be77ca8d3b96e871866460 Mon Sep 17 00:00:00 2001 From: synopss Date: Sat, 10 May 2025 14:36:57 +0200 Subject: [PATCH 1/7] refactor: switching to slf4j logger --- .../replayreader/common/util/LogUtil.java | 24 ------------------- .../replay/controller/ReplayController.java | 8 +++++-- .../ui/controller/MainViewController.java | 6 +++-- .../replayreader/ui/util/DragDropSupport.java | 5 +++- 4 files changed, 14 insertions(+), 29 deletions(-) delete mode 100644 src/main/java/com/synops/replayreader/common/util/LogUtil.java diff --git a/src/main/java/com/synops/replayreader/common/util/LogUtil.java b/src/main/java/com/synops/replayreader/common/util/LogUtil.java deleted file mode 100644 index e8cf4ad..0000000 --- a/src/main/java/com/synops/replayreader/common/util/LogUtil.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.synops.replayreader.common.util; - -import java.io.PrintStream; -import java.text.SimpleDateFormat; -import java.util.Date; - -public class LogUtil { - private static final PrintStream ps; - - private LogUtil() { - } - - public static void debug(String msg) { - ps.printf("%s: %s%n", getTimestamp(), msg); - } - - private static String getTimestamp() { - return (new SimpleDateFormat("mm:ss:SSS")).format(new Date()); - } - - static { - ps = System.out; - } -} diff --git a/src/main/java/com/synops/replayreader/replay/controller/ReplayController.java b/src/main/java/com/synops/replayreader/replay/controller/ReplayController.java index 37d18ce..2b73c91 100644 --- a/src/main/java/com/synops/replayreader/replay/controller/ReplayController.java +++ b/src/main/java/com/synops/replayreader/replay/controller/ReplayController.java @@ -8,14 +8,18 @@ import com.synops.replayreader.replay.model.ReplayCollectionImpl; import com.synops.replayreader.replay.model.ReplayFilter; import com.synops.replayreader.core.event.ReplayProgressEvent; +import com.synops.replayreader.ui.util.DragDropSupport; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @Component public class ReplayController { + private static final Logger LOGGER = LoggerFactory.getLogger(ReplayController.class); private final ReplayReader replayReader; private final ReplayModelBuilder replayModelBuilder; @@ -53,8 +57,8 @@ public ReplayCollection readReplays(List files, BattleType battleType, Con } }); - System.out.printf("%nfinished reading %s replays%n", replays.size()); - System.out.printf("battle type = %s%n", battleType.name()); + LOGGER.info("finished reading {} replays", replays.size()); + LOGGER.debug("battle type = {}", battleType.name()); ReplayFilter replayFilter = ReplayFilter.createDefault().setBattleType(battleType); return new ReplayCollectionImpl(replays, replayFilter); } diff --git a/src/main/java/com/synops/replayreader/ui/controller/MainViewController.java b/src/main/java/com/synops/replayreader/ui/controller/MainViewController.java index f2fdf75..7162bbb 100644 --- a/src/main/java/com/synops/replayreader/ui/controller/MainViewController.java +++ b/src/main/java/com/synops/replayreader/ui/controller/MainViewController.java @@ -6,7 +6,6 @@ import com.synops.replayreader.clan.comparator.ClanListComparator; import com.synops.replayreader.clan.util.ClanStringConverter; import com.synops.replayreader.common.comparator.SortingComparators; -import com.synops.replayreader.common.util.LogUtil; import com.synops.replayreader.core.event.ReplayProgressEvent; import com.synops.replayreader.core.service.DialogService; import com.synops.replayreader.core.service.NotificationService; @@ -50,6 +49,8 @@ import javafx.scene.control.Tooltip; import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.info.BuildProperties; import org.springframework.lang.Nullable; @@ -58,6 +59,7 @@ @Component public class MainViewController { + private static final Logger LOGGER = LoggerFactory.getLogger(MainViewController.class); private static final String REPLAY_READER_BUGS_URL = "https://github.com/synopss/replay-reader/issues/new/choose"; private static final String REPLAY_RELEASES_URL = "https://github.com/synopss/replay-reader/releases/latest"; private final ReplayService replayService; @@ -356,7 +358,7 @@ private void onProgressChanged(ReplayProgressEvent event) { private void onLoadSucceeded(WorkerStateEvent event) { updatePlayers(); updateClans(); - LogUtil.debug("onLoadSucceeded before selectionmodel changes"); + LOGGER.debug("onLoadSucceeded before SelectionModel changes"); playersList.getSelectionModel().selectFirst(); clanChoiceBox.getSelectionModel().selectFirst(); sortingChoiceBox.getSelectionModel().selectFirst(); diff --git a/src/main/java/com/synops/replayreader/ui/util/DragDropSupport.java b/src/main/java/com/synops/replayreader/ui/util/DragDropSupport.java index 23c4725..4fc195a 100644 --- a/src/main/java/com/synops/replayreader/ui/util/DragDropSupport.java +++ b/src/main/java/com/synops/replayreader/ui/util/DragDropSupport.java @@ -5,10 +5,13 @@ import java.util.function.Consumer; import javafx.scene.input.DragEvent; import javafx.scene.input.TransferMode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @Component public class DragDropSupport { + private static final Logger LOGGER = LoggerFactory.getLogger(DragDropSupport.class); private Consumer> loader; @@ -36,7 +39,7 @@ public void onDragDropped(DragEvent event) { loader.accept(db.getFiles()); } - System.out.println("Drag dropped: " + success); + LOGGER.debug("Drag dropped: {}", success); event.setDropCompleted(success); event.consume(); } From 2f49ae9b32baa6cd9fe616ee84778b23bafae54c Mon Sep 17 00:00:00 2001 From: synopss Date: Sat, 10 May 2025 15:16:10 +0200 Subject: [PATCH 2/7] feat: adding auto update --- .../replayreader/JavaFxApplication.java | 22 ++++++++----- .../replayreader/ReplayReaderApplication.java | 3 +- .../com/synops/replayreader/core/Startup.java | 32 +++++++++++++++++++ .../replayreader/core/event/StartupEvent.java | 5 +++ .../ui/controller/MainViewController.java | 13 ++++++++ .../replayreader/update/UpdateClient.java | 4 +-- .../replayreader/update/VersionCheckTask.java | 17 ++++++++++ .../replayreader/update/VersionChecker.java | 12 +++++++ 8 files changed, 96 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/synops/replayreader/core/Startup.java create mode 100644 src/main/java/com/synops/replayreader/core/event/StartupEvent.java create mode 100644 src/main/java/com/synops/replayreader/update/VersionCheckTask.java diff --git a/src/main/java/com/synops/replayreader/JavaFxApplication.java b/src/main/java/com/synops/replayreader/JavaFxApplication.java index f7d95ca..a189c3b 100644 --- a/src/main/java/com/synops/replayreader/JavaFxApplication.java +++ b/src/main/java/com/synops/replayreader/JavaFxApplication.java @@ -13,28 +13,34 @@ public class JavaFxApplication extends Application { - private ConfigurableApplicationContext applicationContext; + private static Class springApplicationClass; + private ConfigurableApplicationContext springContext; + + static void start(String[] args) { + JavaFxApplication.springApplicationClass = ReplayReaderApplication.class; + Application.launch(JavaFxApplication.class, args); + } @Override public void init() { - applicationContext = new SpringApplicationBuilder(ReplayReaderApplication.class) - .initializers(getInitializer()) - .run(); + springContext = new SpringApplicationBuilder(springApplicationClass).initializers( + getInitializer()).run(getParameters().getRaw().toArray(new String[0])); } @Override public void start(Stage stage) { - Objects.requireNonNull(applicationContext); - applicationContext.publishEvent(new StageReadyEvent(stage)); + Objects.requireNonNull(springContext); + springContext.publishEvent(new StageReadyEvent(stage)); } @Override public void stop() { - applicationContext.close(); + springContext.close(); Platform.exit(); } private ApplicationContextInitializer getInitializer() { - return applicationContext -> applicationContext.registerBean(HostServices.class, this::getHostServices); + return applicationContext -> applicationContext.registerBean(HostServices.class, + this::getHostServices); } } diff --git a/src/main/java/com/synops/replayreader/ReplayReaderApplication.java b/src/main/java/com/synops/replayreader/ReplayReaderApplication.java index 3fc36dd..2f18a20 100644 --- a/src/main/java/com/synops/replayreader/ReplayReaderApplication.java +++ b/src/main/java/com/synops/replayreader/ReplayReaderApplication.java @@ -1,12 +1,11 @@ package com.synops.replayreader; -import javafx.application.Application; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class ReplayReaderApplication { public static void main(String[] args) { - Application.launch(JavaFxApplication.class, args); + JavaFxApplication.start(args); } } diff --git a/src/main/java/com/synops/replayreader/core/Startup.java b/src/main/java/com/synops/replayreader/core/Startup.java new file mode 100644 index 0000000..413dbdf --- /dev/null +++ b/src/main/java/com/synops/replayreader/core/Startup.java @@ -0,0 +1,32 @@ +package com.synops.replayreader.core; + +import com.synops.replayreader.core.event.StartupEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.event.ContextClosedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +@Component +public class Startup implements ApplicationRunner { + private static final Logger LOGGER = LoggerFactory.getLogger(Startup.class); + + private final ApplicationEventPublisher publisher; + + public Startup(ApplicationEventPublisher publisher) { + this.publisher = publisher; + } + + @Override + public void run(ApplicationArguments args) throws Exception { + publisher.publishEvent(new StartupEvent()); + } + + @EventListener + public void onApplicationEvent(ContextClosedEvent ignored) { + LOGGER.info("Application is shutting down"); + } +} diff --git a/src/main/java/com/synops/replayreader/core/event/StartupEvent.java b/src/main/java/com/synops/replayreader/core/event/StartupEvent.java new file mode 100644 index 0000000..fd82deb --- /dev/null +++ b/src/main/java/com/synops/replayreader/core/event/StartupEvent.java @@ -0,0 +1,5 @@ +package com.synops.replayreader.core.event; + +public record StartupEvent() { + +} diff --git a/src/main/java/com/synops/replayreader/ui/controller/MainViewController.java b/src/main/java/com/synops/replayreader/ui/controller/MainViewController.java index 7162bbb..eeac49d 100644 --- a/src/main/java/com/synops/replayreader/ui/controller/MainViewController.java +++ b/src/main/java/com/synops/replayreader/ui/controller/MainViewController.java @@ -189,6 +189,7 @@ public void initialize() { bindingSelectedElements(); progressBar.setVisible(false); versionChecker = new VersionChecker(); + versionChecker.scheduleVersionCheck(this::checkForUpdateInBackground); } private void initMenu() { @@ -455,4 +456,16 @@ private void checkForUpdate() { } })).doOnError(dialogService::showAlertError).subscribe(); } + + private void checkForUpdateInBackground() { + LOGGER.info("Checking for update in background"); + updateClient.getLatestVersion().doOnSuccess(versionResponse -> Platform.runLater(() -> { + if (versionChecker.isNewVersionAvailable(versionResponse.tagName(), + buildProperties.getVersion())) { + dialogService.alertConfirm( + MessageFormat.format(resourceBundle.getString("update.new-version"), + versionResponse.tagName().substring(1)), () -> openUrl(REPLAY_RELEASES_URL)); + } + })).doOnError(dialogService::showAlertError).subscribe();; + } } diff --git a/src/main/java/com/synops/replayreader/update/UpdateClient.java b/src/main/java/com/synops/replayreader/update/UpdateClient.java index 14e677e..99be068 100644 --- a/src/main/java/com/synops/replayreader/update/UpdateClient.java +++ b/src/main/java/com/synops/replayreader/update/UpdateClient.java @@ -1,6 +1,6 @@ package com.synops.replayreader.update; -import com.synops.replayreader.core.StageReadyEvent; +import com.synops.replayreader.core.event.StartupEvent; import org.springframework.context.event.EventListener; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Component; @@ -18,7 +18,7 @@ public UpdateClient(WebClient.Builder webClientBuilder) { } @EventListener - public void init(StageReadyEvent event) { + public void init(StartupEvent event) { webClient = webClientBuilder.baseUrl("https://api.github.com/repos/synopss/replay-reader/") .defaultHeaders(HttpHeaders::clear).build(); } diff --git a/src/main/java/com/synops/replayreader/update/VersionCheckTask.java b/src/main/java/com/synops/replayreader/update/VersionCheckTask.java new file mode 100644 index 0000000..a0383af --- /dev/null +++ b/src/main/java/com/synops/replayreader/update/VersionCheckTask.java @@ -0,0 +1,17 @@ +package com.synops.replayreader.update; + +import java.util.TimerTask; + +class VersionCheckTask extends TimerTask { + + private final Runnable runnable; + + public VersionCheckTask(Runnable runnable) { + this.runnable = runnable; + } + + @Override + public void run() { + runnable.run(); + } +} diff --git a/src/main/java/com/synops/replayreader/update/VersionChecker.java b/src/main/java/com/synops/replayreader/update/VersionChecker.java index 75fd450..ef3eb5e 100644 --- a/src/main/java/com/synops/replayreader/update/VersionChecker.java +++ b/src/main/java/com/synops/replayreader/update/VersionChecker.java @@ -1,11 +1,15 @@ package com.synops.replayreader.update; +import java.time.Duration; +import java.util.Timer; +import java.util.concurrent.ThreadLocalRandom; import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; public class VersionChecker { private static final Pattern VERSION_PATTERN = Pattern.compile("^v(\\d+)\\.(\\d+)\\.(\\d+)$"); + private static final long SKEW_SECONDS = 240; private static Version createVersion(String versionString) { if (!versionString.startsWith("v")) { @@ -34,4 +38,12 @@ public boolean isNewVersionAvailable(String newVersionString, String currentVers var newVersion = createVersion(newVersionString); return newVersion.compareTo(currentVersion) > 0; } + + public void scheduleVersionCheck(Runnable runnable) { + var timer = new Timer("Version Update Checker", true); + + var versionCheckTask = new VersionCheckTask(runnable); + var skew = ThreadLocalRandom.current().nextLong(SKEW_SECONDS) - SKEW_SECONDS / 2; + timer.scheduleAtFixedRate(versionCheckTask, 0, Duration.ofDays(1).plus(Duration.ofSeconds(skew)).toMillis()); + } } From 2fb8485b27c426038d8f3a57749ba7475786a30a Mon Sep 17 00:00:00 2001 From: synopss Date: Sat, 10 May 2025 16:36:58 +0200 Subject: [PATCH 3/7] fix: alignement issues since the introduction of AtlantaFx --- .../synops/replayreader/maps/ui/MapListCell.java | 16 +++++++++++----- .../replayreader/player/ui/PlayerListCell.java | 7 +++++++ .../replayreader/vehicle/ui/VehicleListCell.java | 8 ++++++-- src/main/resources/application.properties | 2 +- src/main/resources/views/main.fxml | 4 ++-- 5 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/synops/replayreader/maps/ui/MapListCell.java b/src/main/java/com/synops/replayreader/maps/ui/MapListCell.java index 15f6567..4815872 100644 --- a/src/main/java/com/synops/replayreader/maps/ui/MapListCell.java +++ b/src/main/java/com/synops/replayreader/maps/ui/MapListCell.java @@ -2,8 +2,8 @@ import static com.synops.replayreader.common.util.Constants.OVERALL; -import com.synops.replayreader.common.i18n.I18nUtils; import com.synops.replayreader.common.comparator.PlayerAndVehicleAndMapToInt; +import com.synops.replayreader.common.i18n.I18nUtils; import java.util.MissingResourceException; import java.util.ResourceBundle; import javafx.beans.property.StringProperty; @@ -15,6 +15,8 @@ public class MapListCell extends ListCell { + private static final double CELL_HEIGHT = 20.0; + private final ResourceBundle resourceBundle = I18nUtils.getBundle(); private final PlayerAndVehicleAndMapToInt function; private final StringProperty player; @@ -43,13 +45,17 @@ protected void updateItem(String item, boolean empty) { textCount.setText(String.valueOf(function.apply(player.get(), vehicleValue, item))); textCount.setFill(Color.ORANGE); textFlow.getChildren().addAll(textMap, textCount); - this.setGraphic(textFlow); - this.setTooltip(new Tooltip(this.createTooltippText(item, vehicleValue))); + textFlow.setPrefHeight(CELL_HEIGHT); + textFlow.setMinHeight(CELL_HEIGHT); + textFlow.setMaxHeight(CELL_HEIGHT); + setGraphic(textFlow); + setTooltip(new Tooltip(createTooltippText(item, vehicleValue))); } else { - this.setGraphic(null); + setTooltip(null); + setGraphic(null); } - this.setText(null); + setText(null); } private String createTooltippText(String map, String vehicle) { diff --git a/src/main/java/com/synops/replayreader/player/ui/PlayerListCell.java b/src/main/java/com/synops/replayreader/player/ui/PlayerListCell.java index e485f01..024b15f 100644 --- a/src/main/java/com/synops/replayreader/player/ui/PlayerListCell.java +++ b/src/main/java/com/synops/replayreader/player/ui/PlayerListCell.java @@ -3,14 +3,18 @@ import com.synops.replayreader.common.comparator.PlayerAndVehicleToInt; import com.synops.replayreader.player.model.Player; import java.util.function.Function; +import javafx.geometry.Pos; import javafx.scene.control.ListCell; import javafx.scene.control.Tooltip; import javafx.scene.paint.Color; import javafx.scene.text.Text; +import javafx.scene.text.TextAlignment; import javafx.scene.text.TextFlow; public class PlayerListCell extends ListCell { + private static final double CELL_HEIGHT = 20.0; + private final PlayerAndVehicleToInt battleCountfunction; private final Function playerInfoFunction; @@ -42,6 +46,9 @@ protected void updateItem(String item, boolean empty) { textMatches.setFill(Color.ORANGE); var textFlow = new TextFlow(); textFlow.getChildren().addAll(textPlayer, textClan, textMatches); + textFlow.setPrefHeight(CELL_HEIGHT); + textFlow.setMinHeight(CELL_HEIGHT); + textFlow.setMaxHeight(CELL_HEIGHT); setGraphic(textFlow); setTooltip(new Tooltip(createTooltippText(playerText, clanText, overallBattleCount))); } else { diff --git a/src/main/java/com/synops/replayreader/vehicle/ui/VehicleListCell.java b/src/main/java/com/synops/replayreader/vehicle/ui/VehicleListCell.java index a288919..115545f 100644 --- a/src/main/java/com/synops/replayreader/vehicle/ui/VehicleListCell.java +++ b/src/main/java/com/synops/replayreader/vehicle/ui/VehicleListCell.java @@ -12,6 +12,8 @@ public class VehicleListCell extends ListCell { + private static final double CELL_HEIGHT = 20.0; + private final PlayerAndVehicleToInt function; private final StringProperty selectedPlayer; @@ -37,12 +39,14 @@ protected void updateItem(String item, boolean empty) { var textFlow = new TextFlow(); textMatches.setFill(Color.ORANGE); textFlow.getChildren().addAll(textVehicle, textMatches); + textFlow.setPrefHeight(CELL_HEIGHT); + textFlow.setMinHeight(CELL_HEIGHT); + textFlow.setMaxHeight(CELL_HEIGHT); setGraphic(textFlow); - setText(null); } else { setGraphic(null); - setText(null); setTooltip(null); } + setText(null); } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 9807cd7..48386f3 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,6 +1,6 @@ spring.application.name=replay-reader spring.main.web-application-type=none -replay-reader.ui.height=800 +replay-reader.ui.height=900 replay-reader.ui.width=1200 replay-reader.config.max-players=500 replay-reader.config.max-clans=25 diff --git a/src/main/resources/views/main.fxml b/src/main/resources/views/main.fxml index 97630d0..eb9fe7d 100644 --- a/src/main/resources/views/main.fxml +++ b/src/main/resources/views/main.fxml @@ -48,7 +48,7 @@ - + @@ -150,7 +150,7 @@ - + From 0e81a5ac9c7925ffc8a74f51fe4e87035c7d3b2a Mon Sep 17 00:00:00 2001 From: synopss Date: Sat, 10 May 2025 17:53:48 +0200 Subject: [PATCH 4/7] refactor: reducing code duplication within custom ListCells --- .../replayreader/maps/ui/MapListCell.java | 55 +++++-------- .../player/ui/PlayerListCell.java | 63 +++++++-------- .../replayreader/ui/control/BaseListCell.java | 80 +++++++++++++++++++ .../vehicle/ui/VehicleListCell.java | 45 ++++------- 4 files changed, 148 insertions(+), 95 deletions(-) create mode 100644 src/main/java/com/synops/replayreader/ui/control/BaseListCell.java diff --git a/src/main/java/com/synops/replayreader/maps/ui/MapListCell.java b/src/main/java/com/synops/replayreader/maps/ui/MapListCell.java index 4815872..8895781 100644 --- a/src/main/java/com/synops/replayreader/maps/ui/MapListCell.java +++ b/src/main/java/com/synops/replayreader/maps/ui/MapListCell.java @@ -4,18 +4,13 @@ import com.synops.replayreader.common.comparator.PlayerAndVehicleAndMapToInt; import com.synops.replayreader.common.i18n.I18nUtils; +import com.synops.replayreader.ui.control.BaseListCell; import java.util.MissingResourceException; import java.util.ResourceBundle; import javafx.beans.property.StringProperty; -import javafx.scene.control.ListCell; -import javafx.scene.control.Tooltip; -import javafx.scene.paint.Color; -import javafx.scene.text.Text; import javafx.scene.text.TextFlow; -public class MapListCell extends ListCell { - - private static final double CELL_HEIGHT = 20.0; +public class MapListCell extends BaseListCell { private final ResourceBundle resourceBundle = I18nUtils.getBundle(); private final PlayerAndVehicleAndMapToInt function; @@ -30,36 +25,30 @@ public MapListCell(StringProperty player, StringProperty vehicle, } @Override - protected void updateItem(String item, boolean empty) { - super.updateItem(item, empty); - if (!empty && item != null) { - var vehicleValue = vehicle.get(); - if (OVERALL.equals(vehicleValue)) { - vehicleValue = null; - } - - var textFlow = new TextFlow(); - var textMap = new Text(); - textMap.setText(getMapName(item) + " "); - var textCount = new Text(); - textCount.setText(String.valueOf(function.apply(player.get(), vehicleValue, item))); - textCount.setFill(Color.ORANGE); - textFlow.getChildren().addAll(textMap, textCount); - textFlow.setPrefHeight(CELL_HEIGHT); - textFlow.setMinHeight(CELL_HEIGHT); - textFlow.setMaxHeight(CELL_HEIGHT); - setGraphic(textFlow); - setTooltip(new Tooltip(createTooltippText(item, vehicleValue))); - } else { - setTooltip(null); - setGraphic(null); + protected TextFlow createTextFlow(String item) { + var vehicleValue = vehicle.get(); + if (OVERALL.equals(vehicleValue)) { + vehicleValue = null; } - setText(null); + var textFlow = new TextFlow(); + var textMap = createRegularText(getMapName(item) + " "); + int count = function.apply(player.get(), vehicleValue, item); + var textCount = createCountText(count); + textFlow.getChildren().addAll(textMap, textCount); + + return textFlow; } - private String createTooltippText(String map, String vehicle) { - return getMapName(map) + " (" + function.apply(player.get(), vehicle, map) + ")"; + @Override + protected String createToolTipText(String item) { + var vehicleValue = vehicle.get(); + if (OVERALL.equals(vehicleValue)) { + vehicleValue = null; + } + + int count = function.apply(player.get(), vehicleValue, item); + return getMapName(item) + " (" + count + ")"; } private String getMapName(String map) { diff --git a/src/main/java/com/synops/replayreader/player/ui/PlayerListCell.java b/src/main/java/com/synops/replayreader/player/ui/PlayerListCell.java index 024b15f..77ba183 100644 --- a/src/main/java/com/synops/replayreader/player/ui/PlayerListCell.java +++ b/src/main/java/com/synops/replayreader/player/ui/PlayerListCell.java @@ -1,17 +1,14 @@ package com.synops.replayreader.player.ui; import com.synops.replayreader.common.comparator.PlayerAndVehicleToInt; +import com.synops.replayreader.ui.control.BaseListCell; import com.synops.replayreader.player.model.Player; import java.util.function.Function; -import javafx.geometry.Pos; -import javafx.scene.control.ListCell; -import javafx.scene.control.Tooltip; import javafx.scene.paint.Color; import javafx.scene.text.Text; -import javafx.scene.text.TextAlignment; import javafx.scene.text.TextFlow; -public class PlayerListCell extends ListCell { +public class PlayerListCell extends BaseListCell { private static final double CELL_HEIGHT = 20.0; @@ -25,40 +22,38 @@ public PlayerListCell(PlayerAndVehicleToInt function, } @Override - protected void updateItem(String item, boolean empty) { - super.updateItem(item, empty); - if (!empty && item != null) { - var playerText = item + " "; - var textPlayer = new Text(playerText); - textPlayer.setFill(Color.BLACK); - var clanText = playerInfoFunction.apply(item).getClanAbbrev(); + protected TextFlow createTextFlow(String item) { + var textFlow = new TextFlow(); + var textPlayer = createRegularText(item + " "); - if (!clanText.isEmpty()) { - clanText = "[" + clanText + "]"; - } else { - clanText = " "; - } - - var textClan = new Text(clanText); - textClan.setFill(Color.GRAY); - var overallBattleCount = battleCountfunction.apply(item, null); - var textMatches = new Text(String.valueOf(overallBattleCount)); - textMatches.setFill(Color.ORANGE); - var textFlow = new TextFlow(); - textFlow.getChildren().addAll(textPlayer, textClan, textMatches); - textFlow.setPrefHeight(CELL_HEIGHT); - textFlow.setMinHeight(CELL_HEIGHT); - textFlow.setMaxHeight(CELL_HEIGHT); - setGraphic(textFlow); - setTooltip(new Tooltip(createTooltippText(playerText, clanText, overallBattleCount))); + var clanText = playerInfoFunction.apply(item).getClanAbbrev(); + if (!clanText.isEmpty()) { + clanText = "[" + clanText + "]"; } else { - setGraphic(null); + clanText = " "; } - setText(null); + var textClan = new Text(clanText); + textClan.setFill(Color.GRAY); + + int overallBattleCount = battleCountfunction.apply(item, null); + var textMatches = createCountText(overallBattleCount); + + textFlow.getChildren().addAll(textPlayer, textClan, textMatches); + return textFlow; } - private String createTooltippText(String playerText, String clan, int overallBattleCount) { - return String.format("%s%s(%d)", playerText, clan, overallBattleCount); + @Override + protected String createToolTipText(String item) { + var playerText = item + " "; + var clanText = playerInfoFunction.apply(item).getClanAbbrev(); + if (!clanText.isEmpty()) { + clanText = "[" + clanText + "]"; + } else { + clanText = ""; + } + + int overallBattleCount = battleCountfunction.apply(item, null); + return String.format("%s%s (%d)", playerText, clanText, overallBattleCount); } } diff --git a/src/main/java/com/synops/replayreader/ui/control/BaseListCell.java b/src/main/java/com/synops/replayreader/ui/control/BaseListCell.java new file mode 100644 index 0000000..bddafea --- /dev/null +++ b/src/main/java/com/synops/replayreader/ui/control/BaseListCell.java @@ -0,0 +1,80 @@ +package com.synops.replayreader.ui.control; + +import javafx.scene.control.ListCell; +import javafx.scene.control.Tooltip; +import javafx.scene.paint.Color; +import javafx.scene.text.Text; +import javafx.scene.text.TextFlow; + +public abstract class BaseListCell extends ListCell { + + protected static final double CELL_HEIGHT = 20.0; + protected static Color COUNT_COLOR = Color.ORANGE; + + @Override + protected void updateItem(T item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null) { + setGraphic(null); + setTooltip(null); + setText(null); + return; + } + + var textFlow = createTextFlow(item); + textFlow.setPrefHeight(CELL_HEIGHT); + textFlow.setMinHeight(CELL_HEIGHT); + textFlow.setMaxHeight(CELL_HEIGHT); + setGraphic(textFlow); + + var tooltipText = createToolTipText(item); + if (tooltipText != null && !tooltipText.isEmpty()) { + setTooltip(new Tooltip(tooltipText)); + } else { + setTooltip(null); + } + + setText(null); + } + + /** + * Creates the TextFlow component to display the item. + * + * @param item The item to display + * @return TextFlow with formatted content + */ + protected abstract TextFlow createTextFlow(T item); + + /** + * Creates the tooltip text for the item. + * + * @param item The item to create a tooltip for + * @return Tooltip text or null if no tooltip should be shown + */ + protected String createToolTipText(T item) { + return null; + } + + /** + * Helper method to create a count text with standard formatting. + * + * @param count The count value to display + * @return Formatted Text component + */ + protected Text createCountText(int count) { + var countText = new Text(String.valueOf(count)); + countText.setFill(COUNT_COLOR); + return countText; + } + + /** + * Helper method to create a regular text with a default color. + * + * @param content The text content + * @return Formatted Text component + */ + protected Text createRegularText(String content) { + return new Text(content); + } +} diff --git a/src/main/java/com/synops/replayreader/vehicle/ui/VehicleListCell.java b/src/main/java/com/synops/replayreader/vehicle/ui/VehicleListCell.java index 115545f..4f63093 100644 --- a/src/main/java/com/synops/replayreader/vehicle/ui/VehicleListCell.java +++ b/src/main/java/com/synops/replayreader/vehicle/ui/VehicleListCell.java @@ -3,16 +3,13 @@ import static com.synops.replayreader.common.util.Constants.OVERALL; import com.synops.replayreader.common.comparator.PlayerAndVehicleToInt; +import com.synops.replayreader.ui.control.BaseListCell; import com.synops.replayreader.vehicle.util.TanksUtil; import javafx.beans.property.StringProperty; -import javafx.scene.control.ListCell; -import javafx.scene.paint.Color; import javafx.scene.text.Text; import javafx.scene.text.TextFlow; -public class VehicleListCell extends ListCell { - - private static final double CELL_HEIGHT = 20.0; +public class VehicleListCell extends BaseListCell { private final PlayerAndVehicleToInt function; private final StringProperty selectedPlayer; @@ -23,30 +20,22 @@ public VehicleListCell(StringProperty selectedPlayer, PlayerAndVehicleToInt func } @Override - protected void updateItem(String item, boolean empty) { - super.updateItem(item, empty); - var textVehicle = new Text(); - var textMatches = new Text(); - if (!empty && item != null) { - if (OVERALL.equals(item)) { - textVehicle.setText(item + " "); - textMatches.setText(String.valueOf(function.apply(selectedPlayer.get(), null))); - } else { - textVehicle.setText(TanksUtil.getTankName(item) + " "); - textMatches.setText(String.valueOf(function.apply(selectedPlayer.get(), item))); - } - - var textFlow = new TextFlow(); - textMatches.setFill(Color.ORANGE); - textFlow.getChildren().addAll(textVehicle, textMatches); - textFlow.setPrefHeight(CELL_HEIGHT); - textFlow.setMinHeight(CELL_HEIGHT); - textFlow.setMaxHeight(CELL_HEIGHT); - setGraphic(textFlow); + protected TextFlow createTextFlow(String item) { + var textFlow = new TextFlow(); + Text textVehicle; + int matchCount; + + if (OVERALL.equals(item)) { + textVehicle = createRegularText(item + " "); + matchCount = function.apply(selectedPlayer.get(), null); } else { - setGraphic(null); - setTooltip(null); + textVehicle = createRegularText(TanksUtil.getTankName(item) + " "); + matchCount = function.apply(selectedPlayer.get(), item); } - setText(null); + + var textMatches = createCountText(matchCount); + textFlow.getChildren().addAll(textVehicle, textMatches); + + return textFlow; } } From c4c06b66658b6ccf6319a0e75fa6a342b276d220 Mon Sep 17 00:00:00 2001 From: synopss Date: Sat, 10 May 2025 18:31:39 +0200 Subject: [PATCH 5/7] feat: dark / light modes based on Windows settings --- build.gradle | 2 ++ .../replayreader/core/StageInitializer.java | 22 +++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 2ea1b22..442f59c 100644 --- a/build.gradle +++ b/build.gradle @@ -29,6 +29,7 @@ wrapper { repositories { mavenCentral() + maven { url 'https://jitpack.io' } } dependencies { @@ -45,6 +46,7 @@ dependencies { implementation platform('org.kordamp.ikonli:ikonli-bom:12.4.0') implementation 'org.kordamp.ikonli:ikonli-fluentui-pack:12.4.0' implementation 'org.kordamp.ikonli:ikonli-javafx:12.4.0' + implementation 'com.github.Dansoftowner:jSystemThemeDetector:3.9.1' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } diff --git a/src/main/java/com/synops/replayreader/core/StageInitializer.java b/src/main/java/com/synops/replayreader/core/StageInitializer.java index 9308ec4..4fc3fc8 100644 --- a/src/main/java/com/synops/replayreader/core/StageInitializer.java +++ b/src/main/java/com/synops/replayreader/core/StageInitializer.java @@ -2,6 +2,7 @@ import atlantafx.base.theme.PrimerDark; import atlantafx.base.theme.PrimerLight; +import com.jthemedetecor.OsThemeDetector; import com.synops.replayreader.common.i18n.I18nUtils; import com.synops.replayreader.ui.util.UiUtil; import java.io.IOException; @@ -44,8 +45,7 @@ public void onApplicationEvent(StageReadyEvent event) { fxmlLoader.setControllerFactory(applicationContext::getBean); Parent parent = fxmlLoader.load(); var stage = event.getStage(); -// Application.setUserAgentStylesheet(new PrimerDark().getUserAgentStylesheet()); - Application.setUserAgentStylesheet(new PrimerLight().getUserAgentStylesheet()); + handleDarkMode(); stage.setScene(new Scene(parent, applicationWidth, applicationHeight)); stage.setResizable(false); stage.setTitle(resourceBundle.getString("main.title")); @@ -56,4 +56,22 @@ public void onApplicationEvent(StageReadyEvent event) { throw new RuntimeException(e); } } + + private void handleDarkMode() { + final OsThemeDetector detector = OsThemeDetector.getDetector(); + final boolean isDarkThemeUsed = detector.isDark(); + if (isDarkThemeUsed) { + Application.setUserAgentStylesheet(new PrimerDark().getUserAgentStylesheet()); + } else { + Application.setUserAgentStylesheet(new PrimerLight().getUserAgentStylesheet()); + } + + detector.registerListener(isDark -> { + if (isDark) { + Application.setUserAgentStylesheet(new PrimerDark().getUserAgentStylesheet()); + } else { + Application.setUserAgentStylesheet(new PrimerLight().getUserAgentStylesheet()); + } + }); + } } From ee2c2eadc969d24fc05753f588022034a836e118 Mon Sep 17 00:00:00 2001 From: synopss Date: Sat, 10 May 2025 18:35:31 +0200 Subject: [PATCH 6/7] refactor: typo and removing of useless constant --- .../synops/replayreader/player/ui/PlayerListCell.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/synops/replayreader/player/ui/PlayerListCell.java b/src/main/java/com/synops/replayreader/player/ui/PlayerListCell.java index 77ba183..6d6536f 100644 --- a/src/main/java/com/synops/replayreader/player/ui/PlayerListCell.java +++ b/src/main/java/com/synops/replayreader/player/ui/PlayerListCell.java @@ -10,14 +10,12 @@ public class PlayerListCell extends BaseListCell { - private static final double CELL_HEIGHT = 20.0; - - private final PlayerAndVehicleToInt battleCountfunction; + private final PlayerAndVehicleToInt battleCountFunction; private final Function playerInfoFunction; public PlayerListCell(PlayerAndVehicleToInt function, Function playerInfoFunction) { - this.battleCountfunction = function; + this.battleCountFunction = function; this.playerInfoFunction = playerInfoFunction; } @@ -36,7 +34,7 @@ protected TextFlow createTextFlow(String item) { var textClan = new Text(clanText); textClan.setFill(Color.GRAY); - int overallBattleCount = battleCountfunction.apply(item, null); + int overallBattleCount = battleCountFunction.apply(item, null); var textMatches = createCountText(overallBattleCount); textFlow.getChildren().addAll(textPlayer, textClan, textMatches); @@ -53,7 +51,7 @@ protected String createToolTipText(String item) { clanText = ""; } - int overallBattleCount = battleCountfunction.apply(item, null); + int overallBattleCount = battleCountFunction.apply(item, null); return String.format("%s%s (%d)", playerText, clanText, overallBattleCount); } } From 8eda230dfe82f4ce10d6f3e7649245236657355a Mon Sep 17 00:00:00 2001 From: synopss Date: Sat, 10 May 2025 19:08:06 +0200 Subject: [PATCH 7/7] refactor: switching from an alert into a notification for updates feedbacks --- .../core/service/NotificationService.java | 11 +++++---- .../core/service/NotificationServiceImpl.java | 23 +++++++++++-------- .../ui/controller/MainViewController.java | 17 ++++++++++---- src/main/resources/i18n/messages.properties | 2 ++ .../resources/i18n/messages_fr.properties | 2 ++ 5 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/synops/replayreader/core/service/NotificationService.java b/src/main/java/com/synops/replayreader/core/service/NotificationService.java index 168b81c..9ff0d3c 100644 --- a/src/main/java/com/synops/replayreader/core/service/NotificationService.java +++ b/src/main/java/com/synops/replayreader/core/service/NotificationService.java @@ -1,17 +1,18 @@ package com.synops.replayreader.core.service; import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.Button; import javafx.scene.layout.StackPane; public interface NotificationService { - void alert(String message, StackPane stackPane); + void alert(String message, StackPane stackPane, Button... buttons); - void information(String message, StackPane stackPane); + void information(String message, StackPane stackPane, Button... buttons); - void warning(String message, StackPane stackPane); + void warning(String message, StackPane stackPane, Button... buttons); - void confirmation(String message, StackPane stackPane); + void confirmation(String message, StackPane stackPane, Button... buttons); - void notify(String message, AlertType alertType, StackPane stackPane); + void notify(String message, AlertType alertType, StackPane stackPane, Button... buttons); } diff --git a/src/main/java/com/synops/replayreader/core/service/NotificationServiceImpl.java b/src/main/java/com/synops/replayreader/core/service/NotificationServiceImpl.java index 6e4b73f..8280903 100644 --- a/src/main/java/com/synops/replayreader/core/service/NotificationServiceImpl.java +++ b/src/main/java/com/synops/replayreader/core/service/NotificationServiceImpl.java @@ -11,6 +11,7 @@ import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.Button; import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; import javafx.util.Duration; @@ -28,23 +29,23 @@ public class NotificationServiceImpl implements NotificationService { private final Map> activeNotifications = new HashMap<>(); - public void alert(String message, StackPane stackPane) { - notify(message, AlertType.ERROR, stackPane); + public void alert(String message, StackPane stackPane, Button... buttons) { + notify(message, AlertType.ERROR, stackPane, buttons); } - public void information(String message, StackPane stackPane) { - notify(message, AlertType.INFORMATION, stackPane); + public void information(String message, StackPane stackPane, Button... buttons) { + notify(message, AlertType.INFORMATION, stackPane, buttons); } - public void warning(String message, StackPane stackPane) { - notify(message, AlertType.WARNING, stackPane); + public void warning(String message, StackPane stackPane, Button... buttons) { + notify(message, AlertType.WARNING, stackPane, buttons); } - public void confirmation(String message, StackPane stackPane) { - notify(message, AlertType.CONFIRMATION, stackPane); + public void confirmation(String message, StackPane stackPane, Button... buttons) { + notify(message, AlertType.CONFIRMATION, stackPane, buttons); } - public void notify(String message, AlertType alertType, StackPane stackPane) { + public void notify(String message, AlertType alertType, StackPane stackPane, Button... buttons) { var notificationList = activeNotifications.computeIfAbsent(stackPane, _ -> FXCollections.observableArrayList()); @@ -81,6 +82,10 @@ public void notify(String message, AlertType alertType, StackPane stackPane) { notification.getStyleClass().add(style); } + if (buttons.length > 0) { + notification.setPrimaryActions(buttons); + } + notificationList.add(notification); notification.setOnClose(_ -> { removeNotification(notification, stackPane); diff --git a/src/main/java/com/synops/replayreader/ui/controller/MainViewController.java b/src/main/java/com/synops/replayreader/ui/controller/MainViewController.java index eeac49d..9c6d4da 100644 --- a/src/main/java/com/synops/replayreader/ui/controller/MainViewController.java +++ b/src/main/java/com/synops/replayreader/ui/controller/MainViewController.java @@ -40,6 +40,7 @@ import javafx.concurrent.Task; import javafx.concurrent.WorkerStateEvent; import javafx.fxml.FXML; +import javafx.scene.control.Button; import javafx.scene.control.ChoiceBox; import javafx.scene.control.Label; import javafx.scene.control.ListView; @@ -462,10 +463,18 @@ private void checkForUpdateInBackground() { updateClient.getLatestVersion().doOnSuccess(versionResponse -> Platform.runLater(() -> { if (versionChecker.isNewVersionAvailable(versionResponse.tagName(), buildProperties.getVersion())) { - dialogService.alertConfirm( - MessageFormat.format(resourceBundle.getString("update.new-version"), - versionResponse.tagName().substring(1)), () -> openUrl(REPLAY_RELEASES_URL)); + var newUpdateVersion = versionResponse.tagName().substring(1); + LOGGER.info("Update found: {}", newUpdateVersion); + + var downLoadLink = new Button(resourceBundle.getString("update.download")); + downLoadLink.setOnAction(_ -> openUrl(REPLAY_RELEASES_URL)); + + notificationService.information( + MessageFormat.format(resourceBundle.getString("update.new-version-latest"), newUpdateVersion), + root, downLoadLink); + } else { + LOGGER.info("No update found"); } - })).doOnError(dialogService::showAlertError).subscribe();; + })).doOnError(dialogService::showAlertError).subscribe(); } } diff --git a/src/main/resources/i18n/messages.properties b/src/main/resources/i18n/messages.properties index 68e702c..cdc6aa6 100644 --- a/src/main/resources/i18n/messages.properties +++ b/src/main/resources/i18n/messages.properties @@ -51,6 +51,8 @@ main.footer.corrupted=Corrupted # Update update.latest-already=You already have the latest version. update.new-version=There's a new version available ({0}). Download? +update.new-version-latest=There's a new version available ({0}). +update.download=Download # Maps 01_karelia=Karelia 02_malinovka=Malinovka diff --git a/src/main/resources/i18n/messages_fr.properties b/src/main/resources/i18n/messages_fr.properties index a6cfb96..ab996a4 100644 --- a/src/main/resources/i18n/messages_fr.properties +++ b/src/main/resources/i18n/messages_fr.properties @@ -51,6 +51,8 @@ main.footer.corrupted=Corrompu(s) # Update update.latest-already=Vous disposez déjà de la dernière version. update.new-version=Une nouvelle version est disponible ({0}). Télécharger ? +update.new-version-latest=Une nouvelle version est disponible ({0}). +update.download=Télécharger # Maps 01_karelia=Carélie 02_malinovka=Malinovka