diff --git a/src/main/java/envoy/client/data/Settings.java b/src/main/java/envoy/client/data/Settings.java index 9d43bca..fc16c26 100644 --- a/src/main/java/envoy/client/data/Settings.java +++ b/src/main/java/envoy/client/data/Settings.java @@ -25,7 +25,7 @@ import envoy.util.SerializationUtils; public class Settings { // Actual settings accessible by the rest of the application - private Map> items; + private Map> items; /** * Settings are stored in this file. @@ -93,6 +93,15 @@ public class Settings { */ public void setCurrentTheme(String themeName) { ((SettingsItem) items.get("currentTheme")).set(themeName); } + /** + * @return true if the currently used theme is one of the default themes + * @since Envoy Client v0.1-beta + */ + public boolean isUsingDefaultTheme() { + final var theme = getCurrentTheme(); + return theme.equals("dark") || theme.equals("light"); + } + /** * @return {@code true}, if pressing the {@code Enter} key suffices to send a * message. Otherwise it has to be pressed in conjunction with the diff --git a/src/main/java/envoy/client/ui/ClearableTextField.java b/src/main/java/envoy/client/ui/ClearableTextField.java index f19155b..fbe6926 100644 --- a/src/main/java/envoy/client/ui/ClearableTextField.java +++ b/src/main/java/envoy/client/ui/ClearableTextField.java @@ -11,8 +11,6 @@ import javafx.scene.layout.Background; import javafx.scene.layout.ColumnConstraints; import javafx.scene.layout.GridPane; -import envoy.client.data.Settings; - /** * This class offers a text field that is automatically equipped with a clear * button. @@ -49,10 +47,7 @@ public class ClearableTextField extends GridPane { public ClearableTextField(String text, int size) { // initializing the textField and the button textField = new TextField(text); - clearButton = new Button("", - new ImageView(IconUtil.load( - Settings.getInstance().getCurrentTheme().equals("dark") ? "/icons/clear_button_white.png" : "/icons/clear_button_black.png", - size))); + clearButton = new Button("", new ImageView(IconUtil.loadIconThemeSensitive("clear_button", size))); clearButton.setOnAction(e -> textField.clear()); clearButton.setFocusTraversable(false); clearButton.getStyleClass().clear(); diff --git a/src/main/java/envoy/client/ui/IconUtil.java b/src/main/java/envoy/client/ui/IconUtil.java index b477e53..5324619 100644 --- a/src/main/java/envoy/client/ui/IconUtil.java +++ b/src/main/java/envoy/client/ui/IconUtil.java @@ -2,9 +2,13 @@ package envoy.client.ui; import java.util.EnumMap; import java.util.EnumSet; +import java.util.logging.Level; import javafx.scene.image.Image; +import envoy.client.data.Settings; +import envoy.util.EnvoyLog; + /** * Provides static utility methods for loading icons from the resource * folder. @@ -21,37 +25,115 @@ public class IconUtil { private IconUtil() {} /** - * Loads an icon from the resource folder. + * Loads an image from the resource folder. * * @param path the path to the icon inside the resource folder - * @return the icon + * @return the loaded image * @since Envoy Client v0.1-beta */ - public static Image load(String path) { return new Image(IconUtil.class.getResource(path).toExternalForm()); } - - /** - * Loads an icon from the resource folder and scales it to a given size. - * - * @param path the path to the icon inside the resource folder - * @param size the size to scale the icon to - * @return the scaled icon - * @since Envoy Client v0.1-beta - */ - public static Image load(String path, int size) { - return new Image(IconUtil.class.getResource(path).toExternalForm(), size, size, true, true); + public static Image load(String path) { + Image image = null; + try { + image = new Image(IconUtil.class.getResource(path).toExternalForm()); + } catch (final NullPointerException e) { + EnvoyLog.getLogger(IconUtil.class).log(Level.WARNING, String.format("Could not load image at path %s: ", path), e); + } + return image; } /** + * Loads an image from the resource folder and scales it to the given size. * - * Loads icons specified by an enum. The images have to be named like the + * @param path the path to the icon inside the resource folder + * @param size the size to scale the icon to + * @return the scaled image + * @since Envoy Client v0.1-beta + */ + public static Image load(String path, int size) { + Image image = null; + try { + image = new Image(IconUtil.class.getResource(path).toExternalForm(), size, size, true, true); + } catch (final NullPointerException e) { + EnvoyLog.getLogger(IconUtil.class).log(Level.WARNING, String.format("Could not load image at path %s: ", path), e); + } + return image; + } + + /** + * Loads a {@code .png} image from the sub-folder {@code /icons/} of the + * resource folder.
+ * The suffix {@code .png} is automatically appended. + * + * @param name the image name without the .png suffix + * @return the loaded image + * @since Envoy Client v0.1-beta + * @apiNote let's load a sample image {@code /icons/abc.png}.
+ * To do that, we only have to call {@code IconUtil.loadIcon("abc")} + */ + public static Image loadIcon(String name) { return load("/icons/" + name + ".png"); } + + /** + * Loads a {@code .png} image from the sub-folder {@code /icons/} of the + * resource folder and scales it to the given size.
+ * The suffix {@code .png} is automatically appended. + * + * @param name the image name without the .png suffix + * @param size the size of the image to scale to + * @return the loaded image + * @since Envoy Client v0.1-beta + * @apiNote let's load a sample image {@code /icons/abc.png} in size 16.
+ * To do that, we only have to call + * {@code IconUtil.loadIcon("abc", 16)} + */ + public static Image loadIcon(String name, int size) { return load("/icons/" + name + ".png", size); } + + /** + * Loads a {@code .png} image whose design depends on the currently active theme + * from the sub-folder {@code /icons/dark/} or {@code /icons/light/} of the + * resource folder. + *

+ * The suffix {@code .png} is automatically appended. + * + * @param name the image name without the "black" or "white" suffix and without + * the .png suffix + * @return the loaded image + * @since Envoy Client v0.1-beta + * @apiNote let's take two sample images {@code /icons/dark/abc.png} and + * {@code /icons/light/abc.png}, and load one of them.
+ * To do that theme sensitive, we only have to call + * {@code IconUtil.loadIconThemeSensitive("abc")} + */ + public static Image loadIconThemeSensitive(String name) { return loadIcon(themeSpecificSubFolder() + name); } + + /** + * Loads a {@code .png} image whose design depends on the currently active theme + * from the sub-folder {@code /icons/dark/} or {@code /icons/light/} of the + * resource folder and scales it to the given size. + *

+ * The suffix {@code .png} is automatically appended. + * + * @param name the image name without the .png suffix + * @param size the size of the image to scale to + * @return the loaded image + * @since Envoy Client v0.1-beta + * @apiNote let's take two sample images {@code /icons/dark/abc.png} and + * {@code /icons/light/abc.png}, and load one of them in size 16.
+ * To do that theme sensitive, we only have to call + * {@code IconUtil.loadIconThemeSensitive("abc", 16)} + */ + public static Image loadIconThemeSensitive(String name, int size) { return loadIcon(themeSpecificSubFolder() + name, size); } + + /** + * + * Loads images specified by an enum. The images have to be named like the * lowercase enum constants with {@code .png} extension and be located inside a * folder with the lowercase name of the enum, which must be contained inside - * the {@code /icons} folder. + * the {@code /icons/} folder. * - * @param the enum that specifies the icons to load + * @param the enum that specifies the images to load * @param enumClass the class of the enum - * @param size the size to scale the icons to - * @return a map containing the loaded icons with the corresponding enum + * @param size the size to scale the images to + * @return a map containing the loaded images with the corresponding enum * constants as keys * @since Envoy Client v0.1-beta */ @@ -62,4 +144,17 @@ public class IconUtil { icons.put(e, load(path + e.toString().toLowerCase() + ".png", size)); return icons; } + + /** + * This method should be called if the display of an image depends upon the + * currently active theme.
+ * In case of a default theme, the string returned will be + * ({@code dark/} or {@code light/}), otherwise it will be empty. + * + * @return the theme specific folder + * @since Envoy Client v0.1-beta + */ + public static String themeSpecificSubFolder() { + return Settings.getInstance().isUsingDefaultTheme() ? Settings.getInstance().getCurrentTheme() + "/" : ""; + } } diff --git a/src/main/java/envoy/client/ui/Startup.java b/src/main/java/envoy/client/ui/Startup.java index c505f33..0240e84 100644 --- a/src/main/java/envoy/client/ui/Startup.java +++ b/src/main/java/envoy/client/ui/Startup.java @@ -102,7 +102,7 @@ public final class Startup extends Application { messageStatusCache = new Cache<>(); stage.setTitle("Envoy"); - stage.getIcons().add(IconUtil.load("/icons/envoy_logo.png")); + stage.getIcons().add(IconUtil.loadIcon("envoy_logo")); final var sceneContext = new SceneContext(stage); sceneContext.load(SceneInfo.LOGIN_SCENE); diff --git a/src/main/java/envoy/client/ui/controller/ChatScene.java b/src/main/java/envoy/client/ui/controller/ChatScene.java index bb457d4..956ce21 100644 --- a/src/main/java/envoy/client/ui/controller/ChatScene.java +++ b/src/main/java/envoy/client/ui/controller/ChatScene.java @@ -110,7 +110,8 @@ public final class ChatScene implements Restorable { messageList.setCellFactory(MessageListCellFactory::new); userList.setCellFactory(ContactListCellFactory::new); - settingsButton.setGraphic(new ImageView(IconUtil.load("/icons/settings.png", 16))); + settingsButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("settings", 16))); + voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", 20))); // Listen to received messages eventBus.register(MessageCreationEvent.class, e -> { @@ -222,9 +223,9 @@ public final class ChatScene implements Restorable { if (recorder.isRecording()) { recorder.cancel(); recording = false; - voiceButton.setText("Record Voice Message"); } pendingAttachment = null; + attachmentView.setVisible(false); remainingChars.setVisible(true); remainingChars @@ -262,16 +263,19 @@ public final class ChatScene implements Restorable { try { if (!recording) { recording = true; - Platform.runLater(() -> voiceButton.setText("Recording...")); + Platform.runLater(() -> { + voiceButton.setText("Recording"); + voiceButton.setGraphic(new ImageView(IconUtil.loadIcon("microphone_recording", 24))); + }); recorder.start(); } else { pendingAttachment = new Attachment(recorder.finish(), AttachmentType.VOICE); recording = false; Platform.runLater(() -> { - voiceButton.setText("Record Voice Message"); + voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", 20))); + voiceButton.setText(null); checkPostConditions(false); - attachmentView.setImage(IconUtil.load(settings.getCurrentTheme().equals("dark") ? "/icons/attachment_present_white.png" - : "/icons/attachment_present_black.png", 20)); + attachmentView.setImage(IconUtil.loadIconThemeSensitive("attachment_present", 20)); attachmentView.setVisible(true); }); } @@ -311,7 +315,7 @@ public final class ChatScene implements Restorable { private void checkPostConditions(boolean sendKeyPressed) { if (!postingPermanentlyDisabled) { if (!postButton.isDisabled() && sendKeyPressed) postMessage(); - postButton.setDisable((messageTextArea.getText().isBlank() && pendingAttachment == null) || currentChat == null); + postButton.setDisable(messageTextArea.getText().isBlank() && pendingAttachment == null || currentChat == null); } else { final var noMoreMessaging = "Go online to send messages"; if (!infoLabel.getText().equals(noMoreMessaging)) @@ -373,6 +377,7 @@ public final class ChatScene implements Restorable { if (pendingAttachment != null) { builder.setAttachment(pendingAttachment); pendingAttachment = null; + attachmentView.setVisible(false); } final var message = builder.build(); diff --git a/src/main/resources/fxml/ChatScene.fxml b/src/main/resources/fxml/ChatScene.fxml index 76f77a0..0ecdeb2 100644 --- a/src/main/resources/fxml/ChatScene.fxml +++ b/src/main/resources/fxml/ChatScene.fxml @@ -117,7 +117,7 @@