Merge branch 'develop' into f/groupMessages
This commit is contained in:
commit
11b50e4fe5
@ -245,7 +245,7 @@ org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert
|
|||||||
org.eclipse.jdt.core.formatter.insert_new_line_after_label=insert
|
org.eclipse.jdt.core.formatter.insert_new_line_after_label=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
|
org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert
|
org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=do not insert
|
org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert
|
||||||
org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
|
org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
|
org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert
|
||||||
org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
|
org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert
|
||||||
@ -439,7 +439,7 @@ org.eclipse.jdt.core.formatter.keep_code_block_on_one_line=one_line_if_empty
|
|||||||
org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=true
|
org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=true
|
||||||
org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
|
org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false
|
||||||
org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line=one_line_never
|
org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line=one_line_never
|
||||||
org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line=one_line_never
|
org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line=one_line_if_empty
|
||||||
org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line=one_line_if_single_item
|
org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line=one_line_if_single_item
|
||||||
org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
|
org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false
|
||||||
org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line=one_line_always
|
org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line=one_line_always
|
||||||
@ -450,7 +450,7 @@ org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line=false
|
|||||||
org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line=true
|
org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line=true
|
||||||
org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line=false
|
org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line=false
|
||||||
org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=true
|
org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=true
|
||||||
org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line=one_line_never
|
org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line=one_line_if_empty
|
||||||
org.eclipse.jdt.core.formatter.lineSplit=150
|
org.eclipse.jdt.core.formatter.lineSplit=150
|
||||||
org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
|
org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false
|
||||||
org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
|
org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false
|
||||||
|
64
src/main/java/envoy/client/data/audio/AudioPlayer.java
Normal file
64
src/main/java/envoy/client/data/audio/AudioPlayer.java
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package envoy.client.data.audio;
|
||||||
|
|
||||||
|
import javax.sound.sampled.*;
|
||||||
|
|
||||||
|
import envoy.exception.EnvoyException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays back audio from a byte array.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>AudioPlayer.java</strong><br>
|
||||||
|
* Created: <strong>05.07.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public final class AudioPlayer {
|
||||||
|
|
||||||
|
private final AudioFormat format;
|
||||||
|
private final DataLine.Info info;
|
||||||
|
|
||||||
|
private Clip clip;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the player with the default audio format.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public AudioPlayer() { this(AudioRecorder.DEFAULT_AUDIO_FORMAT); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the player with a given audio format.
|
||||||
|
*
|
||||||
|
* @param format the audio format to use
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public AudioPlayer(AudioFormat format) {
|
||||||
|
this.format = format;
|
||||||
|
info = new DataLine.Info(Clip.class, format);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if audio play back is supported
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public boolean isSupported() { return AudioSystem.isLineSupported(info); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays back an audio clip.
|
||||||
|
*
|
||||||
|
* @param data the data of the clip
|
||||||
|
* @throws EnvoyException if the play back failed
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public void play(byte[] data) throws EnvoyException {
|
||||||
|
try {
|
||||||
|
clip = (Clip) AudioSystem.getLine(info);
|
||||||
|
clip.open(format, data, 0, data.length);
|
||||||
|
clip.start();
|
||||||
|
} catch (final LineUnavailableException e) {
|
||||||
|
throw new EnvoyException("Cannot play back audio", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
122
src/main/java/envoy/client/data/audio/AudioRecorder.java
Normal file
122
src/main/java/envoy/client/data/audio/AudioRecorder.java
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
package envoy.client.data.audio;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import javax.sound.sampled.*;
|
||||||
|
|
||||||
|
import envoy.exception.EnvoyException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records audio and exports it as a byte array.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>AudioRecorder.java</strong><br>
|
||||||
|
* Created: <strong>02.07.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public final class AudioRecorder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default audio format used for recording and play back.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public static final AudioFormat DEFAULT_AUDIO_FORMAT = new AudioFormat(16000, 16, 1, true, false);
|
||||||
|
|
||||||
|
private final AudioFormat format;
|
||||||
|
private final DataLine.Info info;
|
||||||
|
|
||||||
|
private TargetDataLine line;
|
||||||
|
private Path tempFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the recorder with the default audio format.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public AudioRecorder() { this(DEFAULT_AUDIO_FORMAT); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the recorder with a given audio format.
|
||||||
|
*
|
||||||
|
* @param format the audio format to use
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public AudioRecorder(AudioFormat format) {
|
||||||
|
this.format = format;
|
||||||
|
info = new DataLine.Info(TargetDataLine.class, format);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if audio recording is supported
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public boolean isSupported() { return AudioSystem.isLineSupported(info); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {@code true} if the recorder is active
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public boolean isRecording() { return line != null && line.isActive(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the audio recording.
|
||||||
|
*
|
||||||
|
* @throws EnvoyException if starting the recording failed
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public void start() throws EnvoyException {
|
||||||
|
try {
|
||||||
|
|
||||||
|
// Open the line
|
||||||
|
line = (TargetDataLine) AudioSystem.getLine(info);
|
||||||
|
line.open(format);
|
||||||
|
line.start();
|
||||||
|
|
||||||
|
// Prepare temp file
|
||||||
|
tempFile = Files.createTempFile("recording", "wav");
|
||||||
|
|
||||||
|
// Start the recording
|
||||||
|
final var ais = new AudioInputStream(line);
|
||||||
|
AudioSystem.write(ais, AudioFileFormat.Type.WAVE, tempFile.toFile());
|
||||||
|
} catch (IOException | LineUnavailableException e) {
|
||||||
|
throw new EnvoyException("Cannot record voice", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the recording.
|
||||||
|
*
|
||||||
|
* @return the finished recording
|
||||||
|
* @throws EnvoyException if finishing the recording failed
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public byte[] finish() throws EnvoyException {
|
||||||
|
try {
|
||||||
|
line.stop();
|
||||||
|
line.close();
|
||||||
|
final byte[] data = Files.readAllBytes(tempFile);
|
||||||
|
Files.delete(tempFile);
|
||||||
|
return data;
|
||||||
|
} catch (final IOException e) {
|
||||||
|
throw new EnvoyException("Cannot save voice recording", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the active recording.
|
||||||
|
*
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public void cancel() {
|
||||||
|
line.stop();
|
||||||
|
line.close();
|
||||||
|
try {
|
||||||
|
Files.deleteIfExists(tempFile);
|
||||||
|
} catch (IOException e) {}
|
||||||
|
}
|
||||||
|
}
|
11
src/main/java/envoy/client/data/audio/package-info.java
Normal file
11
src/main/java/envoy/client/data/audio/package-info.java
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* Contains classes related to recording and playing back audio clips.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>package-info.java</strong><br>
|
||||||
|
* Created: <strong>05.07.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
package envoy.client.data.audio;
|
@ -51,16 +51,25 @@ public class Receiver extends Thread {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
|
||||||
try {
|
while (true) {
|
||||||
while (true) {
|
try {
|
||||||
// Read object length
|
// Read object length
|
||||||
final byte[] lenBytes = new byte[4];
|
final byte[] lenBytes = new byte[4];
|
||||||
in.read(lenBytes);
|
in.read(lenBytes);
|
||||||
final int len = SerializationUtils.bytesToInt(lenBytes, 0);
|
final int len = SerializationUtils.bytesToInt(lenBytes, 0);
|
||||||
|
logger.log(Level.FINEST, "Expecting object of length " + len + ".");
|
||||||
|
|
||||||
// Read object into byte array
|
// Read object into byte array
|
||||||
final byte[] objBytes = new byte[len];
|
final byte[] objBytes = new byte[len];
|
||||||
in.read(objBytes);
|
final int bytesRead = in.read(objBytes);
|
||||||
|
logger.log(Level.FINEST, "Read " + bytesRead + " bytes.");
|
||||||
|
|
||||||
|
// Catch LV encoding errors
|
||||||
|
if (len != bytesRead) {
|
||||||
|
logger.log(Level.WARNING,
|
||||||
|
String.format("LV encoding violated: expected %d bytes, received %d bytes. Discarding object...", len, bytesRead));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
try (ObjectInputStream oin = new ObjectInputStream(new ByteArrayInputStream(objBytes))) {
|
try (ObjectInputStream oin = new ObjectInputStream(new ByteArrayInputStream(objBytes))) {
|
||||||
final Object obj = oin.readObject();
|
final Object obj = oin.readObject();
|
||||||
@ -75,11 +84,12 @@ public class Receiver extends Thread {
|
|||||||
obj.getClass()));
|
obj.getClass()));
|
||||||
else processor.accept(obj);
|
else processor.accept(obj);
|
||||||
}
|
}
|
||||||
|
} catch (final SocketException e) {
|
||||||
|
// Connection probably closed by client.
|
||||||
|
return;
|
||||||
|
} catch (final Exception e) {
|
||||||
|
logger.log(Level.SEVERE, "Error on receiver thread", e);
|
||||||
}
|
}
|
||||||
} catch (final SocketException e) {
|
|
||||||
// Connection probably closed by client.
|
|
||||||
} catch (final Exception e) {
|
|
||||||
logger.log(Level.SEVERE, "Error on receiver thread", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
49
src/main/java/envoy/client/ui/AudioControl.java
Normal file
49
src/main/java/envoy/client/ui/AudioControl.java
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package envoy.client.ui;
|
||||||
|
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import javafx.scene.control.Alert;
|
||||||
|
import javafx.scene.control.Alert.AlertType;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
|
||||||
|
import envoy.client.data.audio.AudioPlayer;
|
||||||
|
import envoy.exception.EnvoyException;
|
||||||
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables the play back of audio clips through a button.
|
||||||
|
* <p>
|
||||||
|
* Project: <strong>envoy-client</strong><br>
|
||||||
|
* File: <strong>AudioControl.java</strong><br>
|
||||||
|
* Created: <strong>05.07.2020</strong><br>
|
||||||
|
*
|
||||||
|
* @author Kai S. K. Engelbart
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public final class AudioControl extends HBox {
|
||||||
|
|
||||||
|
private AudioPlayer player = new AudioPlayer();
|
||||||
|
|
||||||
|
private static final Logger logger = EnvoyLog.getLogger(AudioControl.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the audio control.
|
||||||
|
*
|
||||||
|
* @param audioData the audio data to play.
|
||||||
|
* @since Envoy Client v0.1-beta
|
||||||
|
*/
|
||||||
|
public AudioControl(byte[] audioData) {
|
||||||
|
var button = new Button("Play");
|
||||||
|
button.setOnAction(e -> {
|
||||||
|
try {
|
||||||
|
player.play(audioData);
|
||||||
|
} catch (EnvoyException ex) {
|
||||||
|
logger.log(Level.SEVERE, "Could not play back audio: ", ex);
|
||||||
|
new Alert(AlertType.ERROR, "Could not play back audio").showAndWait();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
getChildren().add(button);
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,7 @@ import javafx.scene.input.KeyEvent;
|
|||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
|
|
||||||
import envoy.client.data.*;
|
import envoy.client.data.*;
|
||||||
|
import envoy.client.data.audio.AudioRecorder;
|
||||||
import envoy.client.event.MessageCreationEvent;
|
import envoy.client.event.MessageCreationEvent;
|
||||||
import envoy.client.net.Client;
|
import envoy.client.net.Client;
|
||||||
import envoy.client.net.WriteProxy;
|
import envoy.client.net.WriteProxy;
|
||||||
@ -29,7 +30,9 @@ import envoy.client.ui.listcell.MessageControl;
|
|||||||
import envoy.client.ui.listcell.MessageListCellFactory;
|
import envoy.client.ui.listcell.MessageListCellFactory;
|
||||||
import envoy.data.*;
|
import envoy.data.*;
|
||||||
import envoy.event.*;
|
import envoy.event.*;
|
||||||
|
import envoy.data.Attachment.AttachmentType;
|
||||||
import envoy.event.contact.ContactOperation;
|
import envoy.event.contact.ContactOperation;
|
||||||
|
import envoy.exception.EnvoyException;
|
||||||
import envoy.util.EnvoyLog;
|
import envoy.util.EnvoyLog;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -54,6 +57,9 @@ public final class ChatScene implements Restorable {
|
|||||||
@FXML
|
@FXML
|
||||||
private Button postButton;
|
private Button postButton;
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private Button voiceButton;
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private Button settingsButton;
|
private Button settingsButton;
|
||||||
|
|
||||||
@ -74,9 +80,11 @@ public final class ChatScene implements Restorable {
|
|||||||
private WriteProxy writeProxy;
|
private WriteProxy writeProxy;
|
||||||
private SceneContext sceneContext;
|
private SceneContext sceneContext;
|
||||||
|
|
||||||
private boolean postingPermanentlyDisabled = false;
|
private Chat currentChat;
|
||||||
|
private AudioRecorder recorder;
|
||||||
private Chat currentChat;
|
private boolean recording;
|
||||||
|
private Attachment pendingAttachment;
|
||||||
|
private boolean postingPermanentlyDisabled = false;
|
||||||
|
|
||||||
private static final Settings settings = Settings.getInstance();
|
private static final Settings settings = Settings.getInstance();
|
||||||
private static final EventBus eventBus = EventBus.getInstance();
|
private static final EventBus eventBus = EventBus.getInstance();
|
||||||
@ -173,6 +181,8 @@ public final class ChatScene implements Restorable {
|
|||||||
contactLabel.setText(localDB.getUser().getName());
|
contactLabel.setText(localDB.getUser().getName());
|
||||||
MessageControl.setUser(localDB.getUser());
|
MessageControl.setUser(localDB.getUser());
|
||||||
if (!client.isOnline()) updateInfoLabel("You are offline", "infoLabel-info");
|
if (!client.isOnline()) updateInfoLabel("You are offline", "infoLabel-info");
|
||||||
|
|
||||||
|
recorder = new AudioRecorder();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -206,11 +216,20 @@ public final class ChatScene implements Restorable {
|
|||||||
logger.log(Level.WARNING, "Could not read current chat.", e);
|
logger.log(Level.WARNING, "Could not read current chat.", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Discard the pending attachment
|
||||||
|
if (recorder.isRecording()) {
|
||||||
|
recorder.cancel();
|
||||||
|
recording = false;
|
||||||
|
voiceButton.setText("Record Voice Message");
|
||||||
|
}
|
||||||
|
pendingAttachment = null;
|
||||||
|
|
||||||
remainingChars.setVisible(true);
|
remainingChars.setVisible(true);
|
||||||
remainingChars
|
remainingChars
|
||||||
.setText(String.format("remaining chars: %d/%d", MAX_MESSAGE_LENGTH - messageTextArea.getText().length(), MAX_MESSAGE_LENGTH));
|
.setText(String.format("remaining chars: %d/%d", MAX_MESSAGE_LENGTH - messageTextArea.getText().length(), MAX_MESSAGE_LENGTH));
|
||||||
}
|
}
|
||||||
messageTextArea.setDisable(currentChat == null || postingPermanentlyDisabled);
|
messageTextArea.setDisable(currentChat == null || postingPermanentlyDisabled);
|
||||||
|
voiceButton.setDisable(!recorder.isSupported());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -235,6 +254,26 @@ public final class ChatScene implements Restorable {
|
|||||||
sceneContext.<ContactSearchScene>getController().initializeData(sceneContext, localDB);
|
sceneContext.<ContactSearchScene>getController().initializeData(sceneContext, localDB);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FXML
|
||||||
|
private void voiceButtonClicked() {
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
if (!recording) {
|
||||||
|
recording = true;
|
||||||
|
Platform.runLater(() -> voiceButton.setText("Recording..."));
|
||||||
|
recorder.start();
|
||||||
|
} else {
|
||||||
|
pendingAttachment = new Attachment(recorder.finish(), AttachmentType.VOICE);
|
||||||
|
recording = false;
|
||||||
|
Platform.runLater(() -> { voiceButton.setText("Record Voice Message"); checkPostConditions(false); });
|
||||||
|
}
|
||||||
|
} catch (EnvoyException e) {
|
||||||
|
logger.log(Level.SEVERE, "Could not record audio: ", e);
|
||||||
|
Platform.runLater(new Alert(AlertType.ERROR, "Could not record audio")::showAndWait);
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks the text length of the {@code messageTextArea}, adjusts the
|
* Checks the text length of the {@code messageTextArea}, adjusts the
|
||||||
* {@code remainingChars} label and checks whether to send the message
|
* {@code remainingChars} label and checks whether to send the message
|
||||||
@ -257,11 +296,14 @@ public final class ChatScene implements Restorable {
|
|||||||
*/
|
*/
|
||||||
@FXML
|
@FXML
|
||||||
private void checkPostConditions(KeyEvent e) {
|
private void checkPostConditions(KeyEvent e) {
|
||||||
|
checkPostConditions(settings.isEnterToSend() && e.getCode() == KeyCode.ENTER
|
||||||
|
|| !settings.isEnterToSend() && e.getCode() == KeyCode.ENTER && e.isControlDown());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkPostConditions(boolean sendKeyPressed) {
|
||||||
if (!postingPermanentlyDisabled) {
|
if (!postingPermanentlyDisabled) {
|
||||||
if (!postButton.isDisabled() && (settings.isEnterToSend() && e.getCode() == KeyCode.ENTER
|
if (!postButton.isDisabled() && sendKeyPressed) postMessage();
|
||||||
|| !settings.isEnterToSend() && e.getCode() == KeyCode.ENTER && e.isControlDown()))
|
postButton.setDisable((messageTextArea.getText().isBlank() && pendingAttachment == null) || currentChat == null);
|
||||||
postMessage();
|
|
||||||
postButton.setDisable(messageTextArea.getText().isBlank() || 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))
|
||||||
@ -317,11 +359,16 @@ public final class ChatScene implements Restorable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final var text = messageTextArea.getText().strip();
|
final var text = messageTextArea.getText().strip();
|
||||||
if (text.isBlank()) throw new IllegalArgumentException("A message without visible text can not be sent.");
|
|
||||||
try {
|
try {
|
||||||
// Create and send message
|
// 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);
|
.setText(text);
|
||||||
|
// Setting an attachment, if present
|
||||||
|
if (pendingAttachment != null) {
|
||||||
|
builder.setAttachment(pendingAttachment);
|
||||||
|
pendingAttachment = null;
|
||||||
|
}
|
||||||
|
// 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())
|
||||||
: builder.build();
|
: builder.build();
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import javafx.scene.image.Image;
|
|||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
|
|
||||||
|
import envoy.client.ui.AudioControl;
|
||||||
import envoy.client.ui.IconUtil;
|
import envoy.client.ui.IconUtil;
|
||||||
import envoy.data.Message;
|
import envoy.data.Message;
|
||||||
import envoy.data.Message.MessageStatus;
|
import envoy.data.Message.MessageStatus;
|
||||||
@ -38,6 +39,20 @@ public class MessageControl extends VBox {
|
|||||||
public MessageControl(Message message) {
|
public MessageControl(Message message) {
|
||||||
// Creating the underlying VBox, the dateLabel and the textLabel
|
// Creating the underlying VBox, the dateLabel and the textLabel
|
||||||
super(new Label(dateFormat.format(message.getCreationDate())));
|
super(new Label(dateFormat.format(message.getCreationDate())));
|
||||||
|
|
||||||
|
// Handling message attachment display
|
||||||
|
if (message.hasAttachment()) switch (message.getAttachment().getType()) {
|
||||||
|
case PICTURE:
|
||||||
|
break;
|
||||||
|
case VIDEO:
|
||||||
|
break;
|
||||||
|
case VOICE:
|
||||||
|
getChildren().add(new AudioControl(message.getAttachment().getData()));
|
||||||
|
break;
|
||||||
|
case DOCUMENT:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
final var textLabel = new Label(message.getText());
|
final var textLabel = new Label(message.getText());
|
||||||
textLabel.setWrapText(true);
|
textLabel.setWrapText(true);
|
||||||
getChildren().add(textLabel);
|
getChildren().add(textLabel);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
.button, .list-cell {
|
.button, .list-cell, .progress-bar * {
|
||||||
-fx-background-radius: 5.0em;
|
-fx-background-radius: 5.0em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,6 +26,10 @@
|
|||||||
-fx-text-fill: transparent;
|
-fx-text-fill: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.progress-bar{
|
||||||
|
-fx-progress-color: blue;
|
||||||
|
}
|
||||||
|
|
||||||
.online {
|
.online {
|
||||||
-fx-text-fill: limegreen;
|
-fx-text-fill: limegreen;
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
<?import javafx.geometry.Insets?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.control.Button?>
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.ButtonBar?>
|
||||||
<?import javafx.scene.control.ContextMenu?>
|
<?import javafx.scene.control.ContextMenu?>
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.ListView?>
|
<?import javafx.scene.control.ListView?>
|
||||||
@ -18,29 +19,24 @@
|
|||||||
xmlns:fx="http://javafx.com/fxml/1"
|
xmlns:fx="http://javafx.com/fxml/1"
|
||||||
fx:controller="envoy.client.ui.controller.ChatScene">
|
fx:controller="envoy.client.ui.controller.ChatScene">
|
||||||
<columnConstraints>
|
<columnConstraints>
|
||||||
<ColumnConstraints hgrow="SOMETIMES"
|
<ColumnConstraints hgrow="NEVER" minWidth="60.0"
|
||||||
maxWidth="1.7976931348623157E308" minWidth="10.0" percentWidth="25.0"
|
prefWidth="160.0" />
|
||||||
prefWidth="161.0" />
|
<ColumnConstraints hgrow="ALWAYS"
|
||||||
<ColumnConstraints hgrow="SOMETIMES"
|
maxWidth="1.7976931348623157E308" minWidth="10.0" prefWidth="357.0" />
|
||||||
maxWidth="1.7976931348623157E308" minWidth="10.0" percentWidth="65.0"
|
|
||||||
prefWidth="357.0" />
|
|
||||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="10.0"
|
|
||||||
minWidth="10.0" percentWidth="10.0" prefWidth="10.0" />
|
|
||||||
</columnConstraints>
|
</columnConstraints>
|
||||||
<rowConstraints>
|
<rowConstraints>
|
||||||
|
<RowConstraints maxHeight="-Infinity"
|
||||||
|
minHeight="-Infinity" prefHeight="50.0" vgrow="NEVER" />
|
||||||
|
<RowConstraints maxHeight="-Infinity"
|
||||||
|
minHeight="-Infinity" prefHeight="20.0" vgrow="NEVER" />
|
||||||
<RowConstraints maxHeight="1.7976931348623157E308"
|
<RowConstraints maxHeight="1.7976931348623157E308"
|
||||||
minHeight="10.0" percentHeight="10.0" prefHeight="70.0"
|
minHeight="50.0" prefHeight="155.14286150251115" vgrow="ALWAYS" />
|
||||||
vgrow="SOMETIMES" />
|
<RowConstraints maxHeight="-Infinity"
|
||||||
<RowConstraints maxHeight="1.7976931348623157E308"
|
minHeight="-Infinity" prefHeight="20.0" vgrow="NEVER" />
|
||||||
minHeight="10.0" percentHeight="7.0" vgrow="SOMETIMES" />
|
<RowConstraints maxHeight="120.0" minHeight="40.0"
|
||||||
<RowConstraints maxHeight="1.7976931348623157E308"
|
prefHeight="60.0" vgrow="NEVER" />
|
||||||
minHeight="10.0" percentHeight="60.0" prefHeight="50.0"
|
<RowConstraints maxHeight="-Infinity"
|
||||||
vgrow="SOMETIMES" />
|
minHeight="-Infinity" prefHeight="40.0" vgrow="NEVER" />
|
||||||
<RowConstraints maxHeight="50.0" minHeight="10.0"
|
|
||||||
percentHeight="2.0" prefHeight="50.0" vgrow="SOMETIMES" />
|
|
||||||
<RowConstraints maxHeight="1.7976931348623157E308"
|
|
||||||
minHeight="10.0" percentHeight="21.0" prefHeight="100.0"
|
|
||||||
vgrow="SOMETIMES" />
|
|
||||||
</rowConstraints>
|
</rowConstraints>
|
||||||
<children>
|
<children>
|
||||||
<ListView fx:id="userList" onMouseClicked="#userListClicked"
|
<ListView fx:id="userList" onMouseClicked="#userListClicked"
|
||||||
@ -61,8 +57,8 @@
|
|||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
</contextMenu>
|
</contextMenu>
|
||||||
</ListView>
|
</ListView>
|
||||||
<Label fx:id="contactLabel" prefHeight="16.0" prefWidth="250.0"
|
<Label fx:id="contactLabel" prefHeight="27.0" prefWidth="134.0"
|
||||||
text="Select a contact to chat with" GridPane.columnSpan="2">
|
GridPane.columnSpan="2">
|
||||||
<GridPane.margin>
|
<GridPane.margin>
|
||||||
<Insets left="10.0" />
|
<Insets left="10.0" />
|
||||||
</GridPane.margin>
|
</GridPane.margin>
|
||||||
@ -72,8 +68,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.columnSpan="2"
|
GridPane.columnIndex="1" GridPane.halignment="RIGHT"
|
||||||
GridPane.halignment="RIGHT" GridPane.valignment="CENTER">
|
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>
|
||||||
@ -108,39 +104,45 @@
|
|||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
</contextMenu>
|
</contextMenu>
|
||||||
</ListView>
|
</ListView>
|
||||||
<Button fx:id="postButton" defaultButton="true" disable="true"
|
<ButtonBar prefWidth="436.0" GridPane.columnIndex="1"
|
||||||
mnemonicParsing="true" onAction="#postMessage" prefHeight="10.0"
|
GridPane.halignment="CENTER" GridPane.rowIndex="5"
|
||||||
prefWidth="75.0" text="_Post" GridPane.columnIndex="2"
|
|
||||||
GridPane.halignment="CENTER" GridPane.rowIndex="4"
|
|
||||||
GridPane.valignment="BOTTOM">
|
GridPane.valignment="BOTTOM">
|
||||||
<GridPane.margin>
|
<GridPane.margin>
|
||||||
<Insets bottom="10.0" right="10.0" />
|
<Insets right="10.0" top="5.0" />
|
||||||
</GridPane.margin>
|
</GridPane.margin>
|
||||||
<tooltip>
|
<buttons>
|
||||||
<Tooltip anchorLocation="WINDOW_TOP_LEFT" autoHide="true"
|
<Button fx:id="voiceButton" disable="true"
|
||||||
maxWidth="350.0"
|
onAction="#voiceButtonClicked" text="_Record Voice Message" />
|
||||||
text="Click this button to send the message. If it is disabled, you first have to select a contact to send it to. A message may automatically be sent when you press (Ctrl + ) Enter, according to your preferences. Additionally sends a message when pressing "Alt" + "P"."
|
<Button fx:id="postButton" defaultButton="true"
|
||||||
wrapText="true" />
|
disable="true" mnemonicParsing="true" onAction="#postMessage"
|
||||||
</tooltip>
|
prefHeight="10.0" prefWidth="75.0" text="_Post">
|
||||||
<contextMenu>
|
<tooltip>
|
||||||
<ContextMenu anchorLocation="CONTENT_TOP_LEFT">
|
<Tooltip anchorLocation="WINDOW_TOP_LEFT" autoHide="true"
|
||||||
<items>
|
maxWidth="350.0"
|
||||||
<MenuItem mnemonicParsing="false"
|
text="Click this button to send the message. If it is disabled, you first have to select a contact to send it to. A message may automatically be sent when you press (Ctrl + ) Enter, according to your preferences. Additionally sends a message when pressing "Alt" + "P"."
|
||||||
onAction="#copyAndPostMessage" text="Copy and Send" />
|
wrapText="true" />
|
||||||
</items>
|
</tooltip>
|
||||||
</ContextMenu>
|
<contextMenu>
|
||||||
</contextMenu>
|
<ContextMenu anchorLocation="CONTENT_TOP_LEFT">
|
||||||
<padding>
|
<items>
|
||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
<MenuItem mnemonicParsing="false"
|
||||||
</padding>
|
onAction="#copyAndPostMessage" text="Copy and Send" />
|
||||||
</Button>
|
</items>
|
||||||
|
</ContextMenu>
|
||||||
|
</contextMenu>
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
||||||
|
</padding>
|
||||||
|
</Button>
|
||||||
|
</buttons>
|
||||||
|
</ButtonBar>
|
||||||
<TextArea fx:id="messageTextArea" disable="true"
|
<TextArea fx:id="messageTextArea" disable="true"
|
||||||
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.rowIndex="4">
|
||||||
<GridPane.margin>
|
<GridPane.margin>
|
||||||
<Insets bottom="10.0" left="5.0" top="3.0" />
|
<Insets bottom="10.0" left="5.0" right="10.0" top="3.0" />
|
||||||
</GridPane.margin>
|
</GridPane.margin>
|
||||||
<opaqueInsets>
|
<opaqueInsets>
|
||||||
<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" />
|
||||||
@ -148,13 +150,13 @@
|
|||||||
</TextArea>
|
</TextArea>
|
||||||
<Button mnemonicParsing="true"
|
<Button mnemonicParsing="true"
|
||||||
onAction="#addContactButtonClicked" text="_Add Contacts"
|
onAction="#addContactButtonClicked" text="_Add Contacts"
|
||||||
GridPane.halignment="CENTER" GridPane.rowIndex="4"
|
GridPane.halignment="CENTER" GridPane.rowIndex="5"
|
||||||
GridPane.valignment="CENTER">
|
GridPane.valignment="CENTER">
|
||||||
<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" />
|
||||||
</padding>
|
</padding>
|
||||||
<GridPane.margin>
|
<GridPane.margin>
|
||||||
<Insets bottom="5.0" left="10.0" right="5.0" top="5.0" />
|
<Insets bottom="10.0" left="10.0" right="5.0" top="5.0" />
|
||||||
</GridPane.margin>
|
</GridPane.margin>
|
||||||
</Button>
|
</Button>
|
||||||
<Label id="remainingCharsLabel" fx:id="remainingChars"
|
<Label id="remainingCharsLabel" fx:id="remainingChars"
|
||||||
@ -178,7 +180,7 @@
|
|||||||
</tooltip>
|
</tooltip>
|
||||||
</Label>
|
</Label>
|
||||||
<Label fx:id="infoLabel" text="Something happened"
|
<Label fx:id="infoLabel" text="Something happened"
|
||||||
wrapText="true" textFill="#faa007" visible="false"
|
textFill="#faa007" visible="false" wrapText="true"
|
||||||
GridPane.columnIndex="1" GridPane.rowIndex="1">
|
GridPane.columnIndex="1" GridPane.rowIndex="1">
|
||||||
<GridPane.margin>
|
<GridPane.margin>
|
||||||
<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" />
|
||||||
|
Reference in New Issue
Block a user