Merge pull request #183 from informatik-ag-ngl/f/pictures
Add attachment creation support, display picture messages
This commit is contained in:
		@@ -2,7 +2,10 @@ package envoy.client.ui.controller;
 | 
			
		||||
 | 
			
		||||
import java.awt.Toolkit;
 | 
			
		||||
import java.awt.datatransfer.StringSelection;
 | 
			
		||||
import java.io.ByteArrayInputStream;
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.nio.file.Files;
 | 
			
		||||
import java.util.logging.Level;
 | 
			
		||||
import java.util.logging.Logger;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
@@ -12,10 +15,12 @@ import javafx.collections.FXCollections;
 | 
			
		||||
import javafx.fxml.FXML;
 | 
			
		||||
import javafx.scene.control.*;
 | 
			
		||||
import javafx.scene.control.Alert.AlertType;
 | 
			
		||||
import javafx.scene.image.Image;
 | 
			
		||||
import javafx.scene.image.ImageView;
 | 
			
		||||
import javafx.scene.input.KeyCode;
 | 
			
		||||
import javafx.scene.input.KeyEvent;
 | 
			
		||||
import javafx.scene.paint.Color;
 | 
			
		||||
import javafx.stage.FileChooser;
 | 
			
		||||
 | 
			
		||||
import envoy.client.data.*;
 | 
			
		||||
import envoy.client.data.audio.AudioRecorder;
 | 
			
		||||
@@ -60,6 +65,9 @@ public final class ChatScene implements Restorable {
 | 
			
		||||
	@FXML
 | 
			
		||||
	private Button voiceButton;
 | 
			
		||||
 | 
			
		||||
	@FXML
 | 
			
		||||
	private Button attachmentButton;
 | 
			
		||||
 | 
			
		||||
	@FXML
 | 
			
		||||
	private Button settingsButton;
 | 
			
		||||
 | 
			
		||||
@@ -87,12 +95,15 @@ public final class ChatScene implements Restorable {
 | 
			
		||||
	private AudioRecorder	recorder;
 | 
			
		||||
	private boolean			recording;
 | 
			
		||||
	private Attachment		pendingAttachment;
 | 
			
		||||
	private boolean			postingPermanentlyDisabled	= false;
 | 
			
		||||
	private boolean			postingPermanentlyDisabled;
 | 
			
		||||
 | 
			
		||||
	private static final Settings	settings			= Settings.getInstance();
 | 
			
		||||
	private static final EventBus	eventBus			= EventBus.getInstance();
 | 
			
		||||
	private static final Logger		logger				= EnvoyLog.getLogger(ChatScene.class);
 | 
			
		||||
	private static final int		MAX_MESSAGE_LENGTH	= 255;
 | 
			
		||||
	private static final Settings	settings	= Settings.getInstance();
 | 
			
		||||
	private static final EventBus	eventBus	= EventBus.getInstance();
 | 
			
		||||
	private static final Logger		logger		= EnvoyLog.getLogger(ChatScene.class);
 | 
			
		||||
 | 
			
		||||
	private static final Image	DEFAULT_ATTACHMENT_VIEW_IMAGE	= IconUtil.loadIconThemeSensitive("attachment_present", 20);
 | 
			
		||||
	private static final int	MAX_MESSAGE_LENGTH				= 255;
 | 
			
		||||
	private static final int	DEFAULT_ICON_SIZE				= 16;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Initializes the appearance of certain visual components.
 | 
			
		||||
@@ -106,8 +117,10 @@ public final class ChatScene implements Restorable {
 | 
			
		||||
		messageList.setCellFactory(MessageListCellFactory::new);
 | 
			
		||||
		userList.setCellFactory(ContactListCellFactory::new);
 | 
			
		||||
 | 
			
		||||
		settingsButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("settings", 16)));
 | 
			
		||||
		voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", 20)));
 | 
			
		||||
		settingsButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("settings", DEFAULT_ICON_SIZE)));
 | 
			
		||||
		voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
 | 
			
		||||
		attachmentButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("attachment", DEFAULT_ICON_SIZE)));
 | 
			
		||||
		attachmentView.setImage(DEFAULT_ATTACHMENT_VIEW_IMAGE);
 | 
			
		||||
 | 
			
		||||
		// Listen to received messages
 | 
			
		||||
		eventBus.register(MessageCreationEvent.class, e -> {
 | 
			
		||||
@@ -226,7 +239,7 @@ public final class ChatScene implements Restorable {
 | 
			
		||||
				recording = false;
 | 
			
		||||
			}
 | 
			
		||||
			pendingAttachment = null;
 | 
			
		||||
			attachmentView.setVisible(false);
 | 
			
		||||
			updateAttachmentView(false);
 | 
			
		||||
 | 
			
		||||
			remainingChars.setVisible(true);
 | 
			
		||||
			remainingChars
 | 
			
		||||
@@ -234,6 +247,7 @@ public final class ChatScene implements Restorable {
 | 
			
		||||
		}
 | 
			
		||||
		messageTextArea.setDisable(currentChat == null || postingPermanentlyDisabled);
 | 
			
		||||
		voiceButton.setDisable(!recorder.isSupported());
 | 
			
		||||
		attachmentButton.setDisable(false);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
@@ -266,18 +280,17 @@ public final class ChatScene implements Restorable {
 | 
			
		||||
					recording = true;
 | 
			
		||||
					Platform.runLater(() -> {
 | 
			
		||||
						voiceButton.setText("Recording");
 | 
			
		||||
						voiceButton.setGraphic(new ImageView(IconUtil.loadIcon("microphone_recording", 24)));
 | 
			
		||||
						voiceButton.setGraphic(new ImageView(IconUtil.loadIcon("microphone_recording", DEFAULT_ICON_SIZE)));
 | 
			
		||||
					});
 | 
			
		||||
					recorder.start();
 | 
			
		||||
				} else {
 | 
			
		||||
					pendingAttachment	= new Attachment(recorder.finish(), AttachmentType.VOICE);
 | 
			
		||||
					recording			= false;
 | 
			
		||||
					Platform.runLater(() -> {
 | 
			
		||||
						voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", 20)));
 | 
			
		||||
						voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
 | 
			
		||||
						voiceButton.setText(null);
 | 
			
		||||
						checkPostConditions(false);
 | 
			
		||||
						attachmentView.setImage(IconUtil.loadIconThemeSensitive("attachment_present", 20));
 | 
			
		||||
						attachmentView.setVisible(true);
 | 
			
		||||
						updateAttachmentView(true);
 | 
			
		||||
					});
 | 
			
		||||
				}
 | 
			
		||||
			} catch (final EnvoyException e) {
 | 
			
		||||
@@ -287,6 +300,52 @@ public final class ChatScene implements Restorable {
 | 
			
		||||
		}).start();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@FXML
 | 
			
		||||
	private void attachmentButtonClicked() {
 | 
			
		||||
 | 
			
		||||
		// Display file chooser
 | 
			
		||||
		final var fileChooser = new FileChooser();
 | 
			
		||||
		fileChooser.setTitle("Add Attachment");
 | 
			
		||||
		fileChooser.setInitialDirectory(new File(System.getProperty("user.home")));
 | 
			
		||||
		fileChooser.getExtensionFilters()
 | 
			
		||||
			.addAll(new FileChooser.ExtensionFilter("Pictures", "*.png", "*.jpg", "*.bmp", "*.gif"),
 | 
			
		||||
					new FileChooser.ExtensionFilter("Videos", "*.mp4"),
 | 
			
		||||
					new FileChooser.ExtensionFilter("All Files", "*.*"));
 | 
			
		||||
		final var file = fileChooser.showOpenDialog(sceneContext.getStage());
 | 
			
		||||
 | 
			
		||||
		if (file != null) {
 | 
			
		||||
 | 
			
		||||
			// Check max file size
 | 
			
		||||
			if (file.length() > 16E6) {
 | 
			
		||||
				new Alert(AlertType.WARNING, "The selected file exceeds the size limit of 16MB!").showAndWait();
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Get attachment type (default is document)
 | 
			
		||||
			AttachmentType type = AttachmentType.DOCUMENT;
 | 
			
		||||
			switch (fileChooser.getSelectedExtensionFilter().getDescription()) {
 | 
			
		||||
				case "Pictures":
 | 
			
		||||
					type = AttachmentType.PICTURE;
 | 
			
		||||
					break;
 | 
			
		||||
				case "Videos":
 | 
			
		||||
					type = AttachmentType.VIDEO;
 | 
			
		||||
					break;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Create the pending attachment
 | 
			
		||||
			try {
 | 
			
		||||
				final var fileBytes = Files.readAllBytes(file.toPath());
 | 
			
		||||
				pendingAttachment = new Attachment(fileBytes, type);
 | 
			
		||||
				// Setting the preview image as image of the attachmentView
 | 
			
		||||
				if (type == AttachmentType.PICTURE)
 | 
			
		||||
					attachmentView.setImage(new Image(new ByteArrayInputStream(fileBytes), DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE, true, true));
 | 
			
		||||
				attachmentView.setVisible(true);
 | 
			
		||||
			} catch (final IOException e) {
 | 
			
		||||
				new Alert(AlertType.ERROR, "The selected file could not be loaded!").showAndWait();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Checks the text length of the {@code messageTextArea}, adjusts the
 | 
			
		||||
	 * {@code remainingChars} label and checks whether to send the message
 | 
			
		||||
@@ -373,16 +432,16 @@ public final class ChatScene implements Restorable {
 | 
			
		||||
		final var text = messageTextArea.getText().strip();
 | 
			
		||||
		try {
 | 
			
		||||
			// Creating the message and its metadata
 | 
			
		||||
			final var	builder	= new MessageBuilder(localDB.getUser().getID(), currentChat.getRecipient().getID(), localDB.getIDGenerator())
 | 
			
		||||
			final var builder = new MessageBuilder(localDB.getUser().getID(), currentChat.getRecipient().getID(), localDB.getIDGenerator())
 | 
			
		||||
				.setText(text);
 | 
			
		||||
      // Setting an attachment, if present
 | 
			
		||||
      if (pendingAttachment != null) {
 | 
			
		||||
			// Setting an attachment, if present
 | 
			
		||||
			if (pendingAttachment != null) {
 | 
			
		||||
				builder.setAttachment(pendingAttachment);
 | 
			
		||||
				pendingAttachment = null;
 | 
			
		||||
				attachmentView.setVisible(false);
 | 
			
		||||
				updateAttachmentView(false);
 | 
			
		||||
			}
 | 
			
		||||
      // Building the final message
 | 
			
		||||
			final var	message	= currentChat.getRecipient() instanceof Group ? builder.buildGroupMessage((Group) currentChat.getRecipient())
 | 
			
		||||
			// Building the final message
 | 
			
		||||
			final var message = currentChat.getRecipient() instanceof Group ? builder.buildGroupMessage((Group) currentChat.getRecipient())
 | 
			
		||||
					: builder.build();
 | 
			
		||||
 | 
			
		||||
			// Send message
 | 
			
		||||
@@ -428,6 +487,20 @@ public final class ChatScene implements Restorable {
 | 
			
		||||
		infoLabel.setVisible(true);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Updates the {@code attachmentView} in terms of visibility.<br>
 | 
			
		||||
	 * Additionally resets the shown image to
 | 
			
		||||
	 * {@code DEFAULT_ATTACHMENT_VIEW_IMAGE} if another image is currently
 | 
			
		||||
	 * present.
 | 
			
		||||
	 *
 | 
			
		||||
	 * @param visible whether the {@code attachmentView} should be displayed
 | 
			
		||||
	 * @since Envoy Client v0.1-beta
 | 
			
		||||
	 */
 | 
			
		||||
	private void updateAttachmentView(boolean visible) {
 | 
			
		||||
		if (!attachmentView.getImage().equals(DEFAULT_ATTACHMENT_VIEW_IMAGE)) attachmentView.setImage(DEFAULT_ATTACHMENT_VIEW_IMAGE);
 | 
			
		||||
		attachmentView.setVisible(visible);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Context menu actions
 | 
			
		||||
 | 
			
		||||
	@FXML
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ package envoy.client.ui.listcell;
 | 
			
		||||
 | 
			
		||||
import java.awt.Toolkit;
 | 
			
		||||
import java.awt.datatransfer.StringSelection;
 | 
			
		||||
import java.io.ByteArrayInputStream;
 | 
			
		||||
import java.time.format.DateTimeFormatter;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.logging.Level;
 | 
			
		||||
@@ -67,6 +68,7 @@ public class MessageControl extends Label {
 | 
			
		||||
		if (message.hasAttachment()) {
 | 
			
		||||
			switch (message.getAttachment().getType()) {
 | 
			
		||||
				case PICTURE:
 | 
			
		||||
					vbox.getChildren().add(new ImageView(new Image(new ByteArrayInputStream(message.getAttachment().getData()), 256, 256, true, true)));
 | 
			
		||||
					break;
 | 
			
		||||
				case VIDEO:
 | 
			
		||||
					break;
 | 
			
		||||
@@ -90,9 +92,7 @@ public class MessageControl extends Label {
 | 
			
		||||
			statusIcon.setPreserveRatio(true);
 | 
			
		||||
			vbox.getChildren().add(statusIcon);
 | 
			
		||||
			getStyleClass().add("own-message");
 | 
			
		||||
		} else {
 | 
			
		||||
			getStyleClass().add("received-message");
 | 
			
		||||
		}
 | 
			
		||||
		} else getStyleClass().add("received-message");
 | 
			
		||||
		// Adjusting height and weight of the cell to the corresponding ListView
 | 
			
		||||
		paddingProperty().setValue(new Insets(5, 20, 5, 20));
 | 
			
		||||
		setContextMenu(contextMenu);
 | 
			
		||||
 
 | 
			
		||||
@@ -51,7 +51,7 @@
 | 
			
		||||
				<Insets bottom="5.0" left="10.0" />
 | 
			
		||||
			</GridPane.margin>
 | 
			
		||||
			<padding>
 | 
			
		||||
				<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
 | 
			
		||||
				<Insets bottom="5.0" left="5.0" right="2.0" top="5.0" />
 | 
			
		||||
			</padding>
 | 
			
		||||
			<contextMenu>
 | 
			
		||||
				<ContextMenu anchorLocation="CONTENT_TOP_LEFT">
 | 
			
		||||
@@ -82,24 +82,29 @@
 | 
			
		||||
				<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
 | 
			
		||||
			</padding>
 | 
			
		||||
		</Button>
 | 
			
		||||
		<ListView fx:id="messageList" prefHeight="257.0"
 | 
			
		||||
			prefWidth="465.0" GridPane.columnIndex="1"
 | 
			
		||||
		<ListView fx:id="messageList" GridPane.columnIndex="1"
 | 
			
		||||
			GridPane.columnSpan="2147483647" GridPane.rowIndex="1"
 | 
			
		||||
			GridPane.rowSpan="2">
 | 
			
		||||
			<GridPane.margin>
 | 
			
		||||
				<Insets left="5.0" right="10.0" />
 | 
			
		||||
			</GridPane.margin>
 | 
			
		||||
			<padding>
 | 
			
		||||
				<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
 | 
			
		||||
				<Insets bottom="5.0" left="5.0" right="2.0" top="5.0" />
 | 
			
		||||
			</padding>
 | 
			
		||||
		</ListView>
 | 
			
		||||
		<ButtonBar prefWidth="436.0" GridPane.columnIndex="1"
 | 
			
		||||
		<ButtonBar buttonMinWidth="40.0" GridPane.columnIndex="1"
 | 
			
		||||
			GridPane.columnSpan="2147483647" GridPane.halignment="CENTER"
 | 
			
		||||
			GridPane.rowIndex="5" GridPane.valignment="BOTTOM">
 | 
			
		||||
			<GridPane.margin>
 | 
			
		||||
				<Insets right="10.0" />
 | 
			
		||||
			</GridPane.margin>
 | 
			
		||||
			<buttons>
 | 
			
		||||
				<Button fx:id="attachmentButton" disable="true"
 | 
			
		||||
					mnemonicParsing="false" onAction="#attachmentButtonClicked">
 | 
			
		||||
					<padding>
 | 
			
		||||
						<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
 | 
			
		||||
					</padding>
 | 
			
		||||
				</Button>
 | 
			
		||||
				<Button fx:id="voiceButton" disable="true"
 | 
			
		||||
					onAction="#voiceButtonClicked">
 | 
			
		||||
					<padding>
 | 
			
		||||
@@ -175,7 +180,7 @@
 | 
			
		||||
		</Label>
 | 
			
		||||
		<Label fx:id="infoLabel" text="Something happened"
 | 
			
		||||
			textFill="#faa007" visible="false" wrapText="true"
 | 
			
		||||
			GridPane.columnIndex="1" GridPane.rowIndex="1">
 | 
			
		||||
			GridPane.columnIndex="1">
 | 
			
		||||
			<GridPane.margin>
 | 
			
		||||
				<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
 | 
			
		||||
			</GridPane.margin>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								src/main/resources/icons/dark/search.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/main/resources/icons/dark/search.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 10 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								src/main/resources/icons/light/search.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								src/main/resources/icons/light/search.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 8.3 KiB  | 
		Reference in New Issue
	
	Block a user