Merge branch 'develop' into f/groupMessages
34
pom.xml
@ -40,6 +40,18 @@
|
|||||||
<artifactId>javafx-fxml</artifactId>
|
<artifactId>javafx-fxml</artifactId>
|
||||||
<version>11.0.2</version>
|
<version>11.0.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-graphics </artifactId>
|
||||||
|
<version>11</version>
|
||||||
|
<classifier>win</classifier>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openjfx</groupId>
|
||||||
|
<artifactId>javafx-graphics </artifactId>
|
||||||
|
<version>11</version>
|
||||||
|
<classifier>linux</classifier>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@ -61,23 +73,23 @@
|
|||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-assembly-plugin</artifactId>
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
<version>3.2.0</version>
|
<version>3.2.4</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<phase>package</phase>
|
<phase>package</phase>
|
||||||
<goals>
|
<goals>
|
||||||
<goal>single</goal>
|
<goal>shade</goal>
|
||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
<archive>
|
<shadedArtifactAttached>true</shadedArtifactAttached>
|
||||||
<manifest>
|
<sharedClassifierName>envoy</sharedClassifierName>
|
||||||
<mainClass>envoy.client.ui.Startup</mainClass>
|
<transformers>
|
||||||
</manifest>
|
<transformer
|
||||||
</archive>
|
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||||
<descriptorRefs>
|
<mainClass>envoy.client.Main</mainClass>
|
||||||
<descriptorRef>jar-with-dependencies</descriptorRef>
|
</transformer>
|
||||||
</descriptorRefs>
|
</transformers>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
|
30
src/main/java/envoy/client/Main.java
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package envoy.client;
|
||||||
|
|
||||||
|
import javafx.application.Application;
|
||||||
|
|
||||||
|
import envoy.client.ui.Startup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggers application startup.
|
||||||
|
* <p>
|
||||||
|
* To allow Maven shading, the main method has to be separated from the
|
||||||
|
* {@link Startup} class which extends {@link Application}.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>Main.java</strong><br>
|
||||||
|
* Created: <strong>05.07.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public class Main {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the application.
|
||||||
|
*
|
||||||
|
* @param args the command line arguments are processed by the
|
||||||
|
* client configuration
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) { Application.launch(Startup.class, args); }
|
||||||
|
}
|
@ -93,6 +93,15 @@ public class Settings {
|
|||||||
*/
|
*/
|
||||||
public void setCurrentTheme(String themeName) { ((SettingsItem<String>) items.get("currentTheme")).set(themeName); }
|
public void setCurrentTheme(String themeName) { ((SettingsItem<String>) 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
|
* @return {@code true}, if pressing the {@code Enter} key suffices to send a
|
||||||
* message. Otherwise it has to be pressed in conjunction with the
|
* message. Otherwise it has to be pressed in conjunction with the
|
||||||
|
@ -11,8 +11,6 @@ import javafx.scene.layout.Background;
|
|||||||
import javafx.scene.layout.ColumnConstraints;
|
import javafx.scene.layout.ColumnConstraints;
|
||||||
import javafx.scene.layout.GridPane;
|
import javafx.scene.layout.GridPane;
|
||||||
|
|
||||||
import envoy.client.data.Settings;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class offers a text field that is automatically equipped with a clear
|
* This class offers a text field that is automatically equipped with a clear
|
||||||
* button.
|
* button.
|
||||||
@ -49,10 +47,7 @@ public class ClearableTextField extends GridPane {
|
|||||||
public ClearableTextField(String text, int size) {
|
public ClearableTextField(String text, int size) {
|
||||||
// initializing the textField and the button
|
// initializing the textField and the button
|
||||||
textField = new TextField(text);
|
textField = new TextField(text);
|
||||||
clearButton = new Button("",
|
clearButton = new Button("", new ImageView(IconUtil.loadIconThemeSensitive("clear_button", size)));
|
||||||
new ImageView(IconUtil.load(
|
|
||||||
Settings.getInstance().getCurrentTheme().equals("dark") ? "/icons/clear_button_white.png" : "/icons/clear_button_black.png",
|
|
||||||
size)));
|
|
||||||
clearButton.setOnAction(e -> textField.clear());
|
clearButton.setOnAction(e -> textField.clear());
|
||||||
clearButton.setFocusTraversable(false);
|
clearButton.setFocusTraversable(false);
|
||||||
clearButton.getStyleClass().clear();
|
clearButton.getStyleClass().clear();
|
||||||
|
@ -2,9 +2,13 @@ package envoy.client.ui;
|
|||||||
|
|
||||||
import java.util.EnumMap;
|
import java.util.EnumMap;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
import javafx.scene.image.Image;
|
import javafx.scene.image.Image;
|
||||||
|
|
||||||
|
import envoy.client.data.Settings;
|
||||||
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides static utility methods for loading icons from the resource
|
* Provides static utility methods for loading icons from the resource
|
||||||
* folder.
|
* folder.
|
||||||
@ -21,37 +25,115 @@ public class IconUtil {
|
|||||||
private 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
|
* @param path the path to the icon inside the resource folder
|
||||||
* @return the icon
|
* @return the loaded image
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
public static Image load(String path) { return new Image(IconUtil.class.getResource(path).toExternalForm()); }
|
public static Image load(String path) {
|
||||||
|
Image image = null;
|
||||||
/**
|
try {
|
||||||
* Loads an icon from the resource folder and scales it to a given size.
|
image = new Image(IconUtil.class.getResource(path).toExternalForm());
|
||||||
*
|
} catch (final NullPointerException e) {
|
||||||
* @param path the path to the icon inside the resource folder
|
EnvoyLog.getLogger(IconUtil.class).log(Level.WARNING, String.format("Could not load image at path %s: ", path), e);
|
||||||
* @param size the size to scale the icon to
|
}
|
||||||
* @return the scaled icon
|
return image;
|
||||||
* @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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* 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.<br>
|
||||||
|
* 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}.<br>
|
||||||
|
* 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.<br>
|
||||||
|
* 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.<br>
|
||||||
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* 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.<br>
|
||||||
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* 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.<br>
|
||||||
|
* 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
|
* 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
|
* folder with the lowercase name of the enum, which must be contained inside
|
||||||
* the {@code /icons} folder.
|
* the {@code /icons/} folder.
|
||||||
*
|
*
|
||||||
* @param <T> the enum that specifies the icons to load
|
* @param <T> the enum that specifies the images to load
|
||||||
* @param enumClass the class of the enum
|
* @param enumClass the class of the enum
|
||||||
* @param size the size to scale the icons to
|
* @param size the size to scale the images to
|
||||||
* @return a map containing the loaded icons with the corresponding enum
|
* @return a map containing the loaded images with the corresponding enum
|
||||||
* constants as keys
|
* constants as keys
|
||||||
* @since Envoy Client v0.1-beta
|
* @since Envoy Client v0.1-beta
|
||||||
*/
|
*/
|
||||||
@ -62,4 +144,17 @@ public class IconUtil {
|
|||||||
icons.put(e, load(path + e.toString().toLowerCase() + ".png", size));
|
icons.put(e, load(path + e.toString().toLowerCase() + ".png", size));
|
||||||
return icons;
|
return icons;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method should be called if the display of an image depends upon the
|
||||||
|
* currently active theme.<br>
|
||||||
|
* 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() + "/" : "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,7 +108,7 @@ public final class Startup extends Application {
|
|||||||
groupMessageStatusCache = new Cache<>();
|
groupMessageStatusCache = new Cache<>();
|
||||||
|
|
||||||
stage.setTitle("Envoy");
|
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);
|
final var sceneContext = new SceneContext(stage);
|
||||||
sceneContext.load(SceneInfo.LOGIN_SCENE);
|
sceneContext.load(SceneInfo.LOGIN_SCENE);
|
||||||
@ -135,13 +135,4 @@ public final class Startup extends Application {
|
|||||||
logger.log(Level.SEVERE, "Unable to save local files: ", e);
|
logger.log(Level.SEVERE, "Unable to save local files: ", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the application.
|
|
||||||
*
|
|
||||||
* @param args the command line arguments are processed by the
|
|
||||||
* {@link ClientConfig}
|
|
||||||
* @since Envoy Client v0.1-beta
|
|
||||||
*/
|
|
||||||
public static void main(String[] args) { launch(args); }
|
|
||||||
}
|
}
|
||||||
|
@ -75,6 +75,9 @@ public final class ChatScene implements Restorable {
|
|||||||
@FXML
|
@FXML
|
||||||
private MenuItem deleteContactMenuItem;
|
private MenuItem deleteContactMenuItem;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private ImageView attachmentView;
|
||||||
|
|
||||||
private LocalDB localDB;
|
private LocalDB localDB;
|
||||||
private Client client;
|
private Client client;
|
||||||
private WriteProxy writeProxy;
|
private WriteProxy writeProxy;
|
||||||
@ -103,7 +106,8 @@ public final class ChatScene implements Restorable {
|
|||||||
messageList.setCellFactory(MessageListCellFactory::new);
|
messageList.setCellFactory(MessageListCellFactory::new);
|
||||||
userList.setCellFactory(ContactListCellFactory::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
|
// Listen to received messages
|
||||||
eventBus.register(MessageCreationEvent.class, e -> {
|
eventBus.register(MessageCreationEvent.class, e -> {
|
||||||
@ -220,9 +224,9 @@ public final class ChatScene implements Restorable {
|
|||||||
if (recorder.isRecording()) {
|
if (recorder.isRecording()) {
|
||||||
recorder.cancel();
|
recorder.cancel();
|
||||||
recording = false;
|
recording = false;
|
||||||
voiceButton.setText("Record Voice Message");
|
|
||||||
}
|
}
|
||||||
pendingAttachment = null;
|
pendingAttachment = null;
|
||||||
|
attachmentView.setVisible(false);
|
||||||
|
|
||||||
remainingChars.setVisible(true);
|
remainingChars.setVisible(true);
|
||||||
remainingChars
|
remainingChars
|
||||||
@ -260,14 +264,23 @@ public final class ChatScene implements Restorable {
|
|||||||
try {
|
try {
|
||||||
if (!recording) {
|
if (!recording) {
|
||||||
recording = true;
|
recording = true;
|
||||||
Platform.runLater(() -> voiceButton.setText("Recording..."));
|
Platform.runLater(() -> {
|
||||||
|
voiceButton.setText("Recording");
|
||||||
|
voiceButton.setGraphic(new ImageView(IconUtil.loadIcon("microphone_recording", 24)));
|
||||||
|
});
|
||||||
recorder.start();
|
recorder.start();
|
||||||
} else {
|
} else {
|
||||||
pendingAttachment = new Attachment(recorder.finish(), AttachmentType.VOICE);
|
pendingAttachment = new Attachment(recorder.finish(), AttachmentType.VOICE);
|
||||||
recording = false;
|
recording = false;
|
||||||
Platform.runLater(() -> { voiceButton.setText("Record Voice Message"); checkPostConditions(false); });
|
Platform.runLater(() -> {
|
||||||
|
voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", 20)));
|
||||||
|
voiceButton.setText(null);
|
||||||
|
checkPostConditions(false);
|
||||||
|
attachmentView.setImage(IconUtil.loadIconThemeSensitive("attachment_present", 20));
|
||||||
|
attachmentView.setVisible(true);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (EnvoyException e) {
|
} catch (final EnvoyException e) {
|
||||||
logger.log(Level.SEVERE, "Could not record audio: ", e);
|
logger.log(Level.SEVERE, "Could not record audio: ", e);
|
||||||
Platform.runLater(new Alert(AlertType.ERROR, "Could not record audio")::showAndWait);
|
Platform.runLater(new Alert(AlertType.ERROR, "Could not record audio")::showAndWait);
|
||||||
}
|
}
|
||||||
@ -303,7 +316,7 @@ public final class ChatScene implements Restorable {
|
|||||||
private void checkPostConditions(boolean sendKeyPressed) {
|
private void checkPostConditions(boolean sendKeyPressed) {
|
||||||
if (!postingPermanentlyDisabled) {
|
if (!postingPermanentlyDisabled) {
|
||||||
if (!postButton.isDisabled() && sendKeyPressed) postMessage();
|
if (!postButton.isDisabled() && sendKeyPressed) postMessage();
|
||||||
postButton.setDisable((messageTextArea.getText().isBlank() && pendingAttachment == null) || currentChat == null);
|
postButton.setDisable(messageTextArea.getText().isBlank() && pendingAttachment == null || currentChat == null);
|
||||||
} else {
|
} else {
|
||||||
final var noMoreMessaging = "Go online to send messages";
|
final var noMoreMessaging = "Go online to send messages";
|
||||||
if (!infoLabel.getText().equals(noMoreMessaging))
|
if (!infoLabel.getText().equals(noMoreMessaging))
|
||||||
@ -367,6 +380,7 @@ public final class ChatScene implements Restorable {
|
|||||||
if (pendingAttachment != null) {
|
if (pendingAttachment != null) {
|
||||||
builder.setAttachment(pendingAttachment);
|
builder.setAttachment(pendingAttachment);
|
||||||
pendingAttachment = null;
|
pendingAttachment = null;
|
||||||
|
attachmentView.setVisible(false);
|
||||||
}
|
}
|
||||||
// Building the final message
|
// Building the final message
|
||||||
final var message = currentChat.getRecipient() instanceof Group ? builder.buildGroupMessage((Group) currentChat.getRecipient())
|
final var message = currentChat.getRecipient() instanceof Group ? builder.buildGroupMessage((Group) currentChat.getRecipient())
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.button:pressed {
|
.button:pressed {
|
||||||
-fx-background-color: darkgray;
|
-fx-background-color: darkviolet;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button:disabled {
|
.button:disabled {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import javafx.geometry.Insets?>
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.geometry.Rectangle2D?>
|
||||||
<?import javafx.scene.control.Button?>
|
<?import javafx.scene.control.Button?>
|
||||||
<?import javafx.scene.control.ButtonBar?>
|
<?import javafx.scene.control.ButtonBar?>
|
||||||
<?import javafx.scene.control.ContextMenu?>
|
<?import javafx.scene.control.ContextMenu?>
|
||||||
@ -9,6 +10,7 @@
|
|||||||
<?import javafx.scene.control.MenuItem?>
|
<?import javafx.scene.control.MenuItem?>
|
||||||
<?import javafx.scene.control.TextArea?>
|
<?import javafx.scene.control.TextArea?>
|
||||||
<?import javafx.scene.control.Tooltip?>
|
<?import javafx.scene.control.Tooltip?>
|
||||||
|
<?import javafx.scene.image.ImageView?>
|
||||||
<?import javafx.scene.layout.ColumnConstraints?>
|
<?import javafx.scene.layout.ColumnConstraints?>
|
||||||
<?import javafx.scene.layout.GridPane?>
|
<?import javafx.scene.layout.GridPane?>
|
||||||
<?import javafx.scene.layout.RowConstraints?>
|
<?import javafx.scene.layout.RowConstraints?>
|
||||||
@ -23,6 +25,9 @@
|
|||||||
prefWidth="160.0" />
|
prefWidth="160.0" />
|
||||||
<ColumnConstraints hgrow="ALWAYS"
|
<ColumnConstraints hgrow="ALWAYS"
|
||||||
maxWidth="1.7976931348623157E308" minWidth="10.0" prefWidth="357.0" />
|
maxWidth="1.7976931348623157E308" minWidth="10.0" prefWidth="357.0" />
|
||||||
|
<ColumnConstraints hgrow="ALWAYS"
|
||||||
|
maxWidth="1.7976931348623157E308" minWidth="10.0" percentWidth="7.0"
|
||||||
|
prefWidth="357.0" />
|
||||||
</columnConstraints>
|
</columnConstraints>
|
||||||
<rowConstraints>
|
<rowConstraints>
|
||||||
<RowConstraints maxHeight="-Infinity"
|
<RowConstraints maxHeight="-Infinity"
|
||||||
@ -43,7 +48,7 @@
|
|||||||
prefHeight="211.0" prefWidth="300.0" GridPane.rowIndex="1"
|
prefHeight="211.0" prefWidth="300.0" GridPane.rowIndex="1"
|
||||||
GridPane.rowSpan="2147483647">
|
GridPane.rowSpan="2147483647">
|
||||||
<GridPane.margin>
|
<GridPane.margin>
|
||||||
<Insets bottom="10.0" left="10.0" />
|
<Insets bottom="5.0" left="10.0" />
|
||||||
</GridPane.margin>
|
</GridPane.margin>
|
||||||
<padding>
|
<padding>
|
||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
@ -68,8 +73,8 @@
|
|||||||
</Label>
|
</Label>
|
||||||
<Button fx:id="settingsButton" mnemonicParsing="true"
|
<Button fx:id="settingsButton" mnemonicParsing="true"
|
||||||
onAction="#settingsButtonClicked" text="_Settings"
|
onAction="#settingsButtonClicked" text="_Settings"
|
||||||
GridPane.columnIndex="1" GridPane.halignment="RIGHT"
|
GridPane.columnIndex="1" GridPane.columnSpan="2147483647"
|
||||||
GridPane.valignment="CENTER">
|
GridPane.halignment="RIGHT" GridPane.valignment="CENTER">
|
||||||
<GridPane.margin>
|
<GridPane.margin>
|
||||||
<Insets bottom="10.0" right="10.0" top="10.0" />
|
<Insets bottom="10.0" right="10.0" top="10.0" />
|
||||||
</GridPane.margin>
|
</GridPane.margin>
|
||||||
@ -105,17 +110,21 @@
|
|||||||
</contextMenu>
|
</contextMenu>
|
||||||
</ListView>
|
</ListView>
|
||||||
<ButtonBar prefWidth="436.0" GridPane.columnIndex="1"
|
<ButtonBar prefWidth="436.0" GridPane.columnIndex="1"
|
||||||
GridPane.halignment="CENTER" GridPane.rowIndex="5"
|
GridPane.columnSpan="2147483647" GridPane.halignment="CENTER"
|
||||||
GridPane.valignment="BOTTOM">
|
GridPane.rowIndex="5" GridPane.valignment="BOTTOM">
|
||||||
<GridPane.margin>
|
<GridPane.margin>
|
||||||
<Insets right="10.0" top="5.0" />
|
<Insets right="10.0" />
|
||||||
</GridPane.margin>
|
</GridPane.margin>
|
||||||
<buttons>
|
<buttons>
|
||||||
<Button fx:id="voiceButton" disable="true"
|
<Button fx:id="voiceButton" disable="true"
|
||||||
onAction="#voiceButtonClicked" text="_Record Voice Message" />
|
onAction="#voiceButtonClicked">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
</Button>
|
||||||
<Button fx:id="postButton" defaultButton="true"
|
<Button fx:id="postButton" defaultButton="true"
|
||||||
disable="true" mnemonicParsing="true" onAction="#postMessage"
|
disable="true" mnemonicParsing="true" onAction="#postMessage"
|
||||||
prefHeight="10.0" prefWidth="75.0" text="_Post">
|
text="_Post">
|
||||||
<tooltip>
|
<tooltip>
|
||||||
<Tooltip anchorLocation="WINDOW_TOP_LEFT" autoHide="true"
|
<Tooltip anchorLocation="WINDOW_TOP_LEFT" autoHide="true"
|
||||||
maxWidth="350.0"
|
maxWidth="350.0"
|
||||||
@ -140,7 +149,8 @@
|
|||||||
onInputMethodTextChanged="#messageTextUpdated"
|
onInputMethodTextChanged="#messageTextUpdated"
|
||||||
onKeyPressed="#checkPostConditions" onKeyTyped="#checkKeyCombination"
|
onKeyPressed="#checkPostConditions" onKeyTyped="#checkKeyCombination"
|
||||||
prefHeight="200.0" prefWidth="200.0" wrapText="true"
|
prefHeight="200.0" prefWidth="200.0" wrapText="true"
|
||||||
GridPane.columnIndex="1" GridPane.rowIndex="4">
|
GridPane.columnIndex="1" GridPane.columnSpan="2147483647"
|
||||||
|
GridPane.rowIndex="4">
|
||||||
<GridPane.margin>
|
<GridPane.margin>
|
||||||
<Insets bottom="10.0" left="5.0" right="10.0" top="3.0" />
|
<Insets bottom="10.0" left="5.0" right="10.0" top="3.0" />
|
||||||
</GridPane.margin>
|
</GridPane.margin>
|
||||||
@ -156,7 +166,7 @@
|
|||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
</padding>
|
</padding>
|
||||||
<GridPane.margin>
|
<GridPane.margin>
|
||||||
<Insets bottom="10.0" left="10.0" right="5.0" top="5.0" />
|
<Insets bottom="10.0" left="10.0" right="5.0" />
|
||||||
</GridPane.margin>
|
</GridPane.margin>
|
||||||
</Button>
|
</Button>
|
||||||
<Label id="remainingCharsLabel" fx:id="remainingChars"
|
<Label id="remainingCharsLabel" fx:id="remainingChars"
|
||||||
@ -189,5 +199,16 @@
|
|||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
</padding>
|
</padding>
|
||||||
</Label>
|
</Label>
|
||||||
|
<ImageView fx:id="attachmentView" pickOnBounds="true"
|
||||||
|
preserveRatio="true" visible="false" GridPane.columnIndex="1"
|
||||||
|
GridPane.columnSpan="2147483647" GridPane.halignment="RIGHT"
|
||||||
|
GridPane.rowIndex="3">
|
||||||
|
<viewport>
|
||||||
|
<Rectangle2D height="20.0" width="20.0" />
|
||||||
|
</viewport>
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets bottom="5.0" right="10.0" top="5.0" />
|
||||||
|
</GridPane.margin>
|
||||||
|
</ImageView>
|
||||||
</children>
|
</children>
|
||||||
</GridPane>
|
</GridPane>
|
||||||
|
BIN
src/main/resources/icons/dark/attachment.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
src/main/resources/icons/dark/attachment_present.png
Normal file
After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
BIN
src/main/resources/icons/dark/microphone.png
Normal file
After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
BIN
src/main/resources/icons/light/attachment.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
src/main/resources/icons/light/attachment_present.png
Normal file
After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
BIN
src/main/resources/icons/light/forward.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
src/main/resources/icons/light/microphone.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
src/main/resources/icons/light/settings.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
src/main/resources/icons/microphone_recording.png
Normal file
After Width: | Height: | Size: 20 KiB |