feat: connect user list to ui

This commit is contained in:
Arnaud Vergnet 2020-12-09 12:19:16 +01:00
parent 733d755c60
commit c5d56a2604
13 changed files with 118 additions and 247 deletions

View file

@ -1,25 +1,19 @@
package fr.insa.clavardator;
import fr.insa.clavardator.network.ConnectionListener;
import fr.insa.clavardator.network.NetDiscoverer;
import fr.insa.clavardator.ui.MainController;
import fr.insa.clavardator.users.PeerUser;
import fr.insa.clavardator.users.User;
import fr.insa.clavardator.users.UserList;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.net.InetAddress;
// See here : https://github.com/jfoenixadmin/JFoenix/blob/master/demo/src/main/java/demos/components/DrawerDemo.java
public class MainApp extends Application {
NetDiscoverer netDiscoverer;
private ConnectionListener connectionListener;
private PeerUser user1;
private PeerUser user2;
private UserList userList;
public static void main(String[] args) {
launch(args);
@ -29,8 +23,11 @@ public class MainApp extends Application {
public void start(Stage stage) throws Exception {
final FXMLLoader mainLoader = new FXMLLoader(getClass().getResource("ui/scene.fxml"));
final MainController main = mainLoader.getController();
final Parent content = mainLoader.load();
MainController mainController = mainLoader.getController();
userList = new UserList();
mainController.setUserList(userList);
Scene scene = new Scene(content);
@ -40,41 +37,11 @@ public class MainApp extends Application {
stage.setMinWidth(800);
stage.setMaximized(true);
stage.show();
netDiscoverer = new NetDiscoverer();
// Network discovery test
netDiscoverer.startDiscoveryListening("Bob", null, Throwable::printStackTrace);
netDiscoverer.discoverActiveUsers("Broadcast",
(ipAddr, data) -> System.out.println("User detected at address : " + ipAddr.toString() + " with name " + data),
Throwable::printStackTrace);
// TCP communication tests
connectionListener = new ConnectionListener();
connectionListener.acceptConnection(socket -> user1.connect(socket,
() -> System.out.println("Connexion établie avec " + user2.getUsername()),
Throwable::printStackTrace),
e -> {
if (e instanceof java.io.EOFException) {
System.out.println("Connexion terminée");
} else {
e.printStackTrace();
}
});
user2 = new PeerUser(InetAddress.getLocalHost().hashCode(), "Yohan");
user2.connect(InetAddress.getByName("ADDRESSE_IP"),
() -> System.out.println("Connexion établie avec " + user2.getUsername()),
Throwable::printStackTrace);
}
@Override
public void stop() throws Exception {
netDiscoverer.stopDiscovery();
connectionListener.stopAccepting();
user1.disconnect();
user2.disconnect();
userList.destroy();
super.stop();
}
}

View file

@ -7,6 +7,8 @@ import fr.insa.clavardator.ui.dialogs.EditUsernameDialogController;
import fr.insa.clavardator.ui.dialogs.SnackbarController;
import fr.insa.clavardator.ui.users.UserListController;
import fr.insa.clavardator.users.CurrentUser;
import fr.insa.clavardator.users.UserList;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
@ -31,28 +33,34 @@ public class MainController implements Initializable {
@FXML
private AboutDialogController aboutDialogController;
@FXML
private UserListController userListController;
private UserListController listController;
@FXML
private ChatController chatController;
@FXML
private LoadingScreenController loadingController;
@FXML
private ErrorScreenController errorController;
private JFXSnackbar snackbar;
private UserList userList;
public MainController() {
currentUser = CurrentUser.getInstance();
currentUser.addObserver(propertyChangeEvent -> {
if (propertyChangeEvent.getPropertyName().equals("state")) {
final CurrentUser.State state = (CurrentUser.State) propertyChangeEvent.getNewValue();
if (state == CurrentUser.State.VALID)
showChat();
else
showLogin(state == CurrentUser.State.INVALID);
final CurrentUser.State newState = (CurrentUser.State) propertyChangeEvent.getNewValue();
onCurrentUserStateChange(newState);
}
});
}
private void onCurrentUserStateChange(CurrentUser.State newState) {
if (newState == CurrentUser.State.VALID)
startChat();
else
Platform.runLater(() -> showLogin(newState == CurrentUser.State.INVALID));
}
private void openEditUsernameDialog(EditUsernameDialogController.Mode mode) {
editUserDialogController.setOnDismissListener(() -> {
if (mode == EditUsernameDialogController.Mode.INITIAL) {
@ -81,6 +89,17 @@ public class MainController implements Initializable {
aboutDialogController.show(root);
}
private void discoverActiveUsers() {
if (userList != null)
userList.discoverActiveUsers((e) -> CurrentUser.getInstance().setState(CurrentUser.State.INVALID));
}
private void startChat() {
discoverActiveUsers();
userList.startDiscoveryListening();
Platform.runLater(this::showChat);
}
private void showChat() {
loadingController.hide();
mainContainer.setVisible(true);
@ -110,11 +129,16 @@ public class MainController implements Initializable {
showError();
}
userListController.addRefreshUserListener(() -> System.out.println("refresh event"));
userListController.addUserSelectedListener((user) -> chatController.setRemoteUser(user));
listController.setUserSelectedListener((user) -> chatController.setRemoteUser(user));
chatController.addAttachmentListener(() -> System.out.println("attach event"));
chatController.addSendListener((text) -> System.out.println("sent : " + text));
toolbarController.addEditListener(() -> openEditUsernameDialog(EditUsernameDialogController.Mode.EDIT));
toolbarController.addAboutListener(this::openAboutDialog);
}
public void setUserList(UserList userList) {
this.userList = userList;
listController.setUserList(userList);
listController.setRefreshUserListener(this::discoverActiveUsers);
}
}

View file

@ -5,7 +5,6 @@ import fr.insa.clavardator.chat.Message;
import fr.insa.clavardator.ui.ButtonPressEvent;
import fr.insa.clavardator.ui.LoadingScreenController;
import fr.insa.clavardator.ui.NoSelectionModel;
import fr.insa.clavardator.users.ActiveUser;
import fr.insa.clavardator.users.PeerUser;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
@ -15,7 +14,6 @@ import javafx.scene.layout.VBox;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.ResourceBundle;
public class ChatController implements Initializable {
@ -97,7 +95,7 @@ public class ChatController implements Initializable {
public void initialize(URL url, ResourceBundle rb) {
loadingController.getRootStyle().clear();
loadingController.getRootStyle().add("inner");
messageList.setSelectionModel(new NoSelectionModel<Message>());
messageList.setSelectionModel(new NoSelectionModel<>());
messageList.setCellFactory(listView -> new MessageListItemCell());
setState(State.INITIAL);
}

View file

@ -1,7 +1,6 @@
package fr.insa.clavardator.ui.users;
import fr.insa.clavardator.users.ActiveUser;
import fr.insa.clavardator.users.User;
import fr.insa.clavardator.users.PeerUser;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.shape.Circle;
@ -19,9 +18,9 @@ public class UserActiveIndicatorController implements Initializable {
}
public void setUser(User user) {
public void setUser(PeerUser user) {
circle.getStyleClass().clear();
if (user instanceof ActiveUser) {
if (user.isActive()) {
circle.getStyleClass().add("active-user-dot");
} else {
circle.getStyleClass().add("inactive-user-dot");

View file

@ -4,69 +4,65 @@ import fr.insa.clavardator.ui.ButtonPressEvent;
import fr.insa.clavardator.ui.UserSelectedEvent;
import fr.insa.clavardator.users.PeerUser;
import fr.insa.clavardator.users.User;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import fr.insa.clavardator.users.UserList;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.ListView;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
public class UserListController implements Initializable {
final private List<ButtonPressEvent> refreshUserListeners;
final private List<UserSelectedEvent> userSelectedListeners;
private ButtonPressEvent refreshUserListener;
private UserSelectedEvent userSelectedListener;
@FXML
private ListView<PeerUser> userList;
private ListView<PeerUser> peerUserListView;
private UserList userList;
public UserListController() {
super();
refreshUserListeners = new ArrayList<>();
userSelectedListeners = new ArrayList<>();
}
public void addRefreshUserListener(ButtonPressEvent listener) {
refreshUserListeners.add(listener);
public void setRefreshUserListener(ButtonPressEvent listener) {
refreshUserListener = listener;
}
public void addUserSelectedListener(UserSelectedEvent listener) {
userSelectedListeners.add(listener);
public void setUserSelectedListener(UserSelectedEvent listener) {
userSelectedListener = listener;
}
public void onRefreshUserListPress() {
refreshUserListeners.forEach(ButtonPressEvent::onPress);
refreshUserListener.onPress();
}
private void onUserSelected(@org.jetbrains.annotations.NotNull PeerUser user) {
final User currentSelectedUser = userList.getSelectionModel().getSelectedItem();
final User currentSelectedUser = peerUserListView.getSelectionModel().getSelectedItem();
if (!user.equals(currentSelectedUser)) {
userList.getSelectionModel().select(user);
userSelectedListeners.forEach(l -> l.onSelected(user));
peerUserListView.getSelectionModel().select(user);
userSelectedListener.onSelected(user);
}
}
@Override
public void initialize(URL url, ResourceBundle rb) {
ObservableList<PeerUser> activeList = FXCollections.observableArrayList(
new PeerUser(0, "Dodo0"),
new PeerUser(1, "Dodo3"),
new PeerUser(2, "Dodo2"),
new PeerUser(3, "Coucou0"),
new PeerUser(4, "Coucou1"),
new PeerUser(5, "Coucou3"),
new PeerUser(6, "Dodo1"),
new PeerUser(7, "Coucou2")
);
activeList.sort(null);
userList.setItems(activeList);
userList.setCellFactory(listView -> {
peerUserListView.setCellFactory(listView -> {
final UserListItemCell cell = new UserListItemCell();
cell.setOnUserSelectedListener(this::onUserSelected);
return cell;
});
}
private void onUserConnected(PeerUser user) {
Platform.runLater(() -> peerUserListView.getItems().add(user));
}
private void onUserDisconnected(PeerUser user) {
Platform.runLater(() -> peerUserListView.getItems().remove(user));
}
public void setUserList(UserList userList) {
this.userList = userList;
userList.addActiveUsersObserver(this::onUserConnected, this::onUserDisconnected);
}
}

View file

@ -2,8 +2,7 @@ package fr.insa.clavardator.ui.users;
import com.jfoenix.controls.JFXButton;
import fr.insa.clavardator.ui.ButtonPressEvent;
import fr.insa.clavardator.users.ActiveUser;
import fr.insa.clavardator.users.User;
import fr.insa.clavardator.users.PeerUser;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
@ -19,7 +18,7 @@ public class UserListItemController implements Initializable {
private ButtonPressEvent listener;
private User user;
private PeerUser user;
public void setOnPressListener(ButtonPressEvent listener) {
this.listener = listener;
@ -43,7 +42,7 @@ public class UserListItemController implements Initializable {
resetBackground();
}
public void setUser(User user) {
public void setUser(PeerUser user) {
this.user = user;
indicatorController.setUser(user);
button.setText(user.getUsername());
@ -51,7 +50,7 @@ public class UserListItemController implements Initializable {
}
private void resetBackground() {
if (user != null && user instanceof ActiveUser) {
if (user != null && user.isActive()) {
button.getStyleClass().remove("inactive-user-item");
button.getStyleClass().add("active-user-item");
} else {

View file

@ -1,116 +0,0 @@
package fr.insa.clavardator.users;
import fr.insa.clavardator.chat.Message;
import fr.insa.clavardator.network.PeerConnection;
import fr.insa.clavardator.util.ErrorCallback;
import java.io.EOFException;
import java.net.InetAddress;
import java.net.Socket;
@Deprecated
public class ActiveUser extends PeerUser {
private final transient PeerConnection connection;
/**
* Asynchronously creates a new ActiveUser by receiving information (id, username)
*
* @param ipAddr The IP address of the new user
* @param callback The function to call on success, with the new ActiveUser as parameter
* @param errorCallback The function to call on socket error
*/
public static void create(InetAddress ipAddr, UserConnectedCallback callback, ErrorCallback errorCallback) {
// Connect to the peer
new PeerConnection(ipAddr, (thisConnection) -> {
// Send our username
String currentUserUsername = CurrentUser.getInstance().getUsername();
int currentUserId = CurrentUser.getInstance().getId();
thisConnection.send(new UserInformation(currentUserId, currentUserUsername), null, errorCallback);
// Receive peer's username
thisConnection.receiveOne(msg -> {
assert msg instanceof UserInformation;
UserInformation userInfo = (UserInformation) msg;
ActiveUser user = new ActiveUser(userInfo.getId(), userInfo.getUsername(), thisConnection, errorCallback);
callback.onUserConnected();
}, errorCallback);
}, errorCallback);
}
/**
* Asynchronously creates a new ActiveUser by receiving information (id, username)
*
* @param socket A socket already connected to the user
* @param callback The function to call on success, with the new ActiveUser as parameter
* @param errorCallback The function to call on socket error
*/
public static void create(Socket socket, UserConnectedCallback callback, ErrorCallback errorCallback) {
PeerConnection connection = new PeerConnection(socket);
// Send our username
String currentUserUsername = CurrentUser.getInstance().getUsername();
int currentUserId = CurrentUser.getInstance().getId();
connection.send(new UserInformation(currentUserId, currentUserUsername), null, errorCallback);
// Receive peer's username
connection.receiveOne(msg -> {
assert msg instanceof UserInformation;
UserInformation userInfo = (UserInformation) msg;
ActiveUser user = new ActiveUser(userInfo.getId(), userInfo.getUsername(), connection, errorCallback);
callback.onUserConnected();
}, errorCallback);
}
/**
* Sends a message to this peer
*
* @param message The message to send
* @param callback The function to call when the message is sent
* @param errorCallback The function to call on error
*/
public void sendMessage(Message message, PeerConnection.MessageSentCallback callback, ErrorCallback errorCallback) {
connection.send(message, callback, errorCallback);
}
/**
* Closes the connection with the user.
* Must be called before exiting the app.
*/
public void destroy() {
connection.close();
}
private ActiveUser(int id, String username, PeerConnection connection, ErrorCallback errorCallback) {
super(id, username);
this.connection = connection;
subscribeToMessages(errorCallback);
}
private void subscribeToMessages(ErrorCallback errorCallback) {
connection.receive(
msg -> {
if (msg.getClass().isInstance(UserInformation.class)) {
assert ((UserInformation) msg).getId() == getId();
setUsername(((UserInformation) msg).getUsername());
} else if (msg.getClass().isInstance(Message.class)) {
assert ((Message) msg).getRecipient() != this;
history.addMessage((Message) msg);
}
},
e -> {
if (e.getClass().isInstance(EOFException.class)) {
// TODO: notify disconnection
} else {
errorCallback.onError(e);
}
});
}
}

View file

@ -35,7 +35,7 @@ public class CurrentUser extends User {
} else {
throw new SocketException();
}
// TODO replace by db and network calls
// TODO place by db username fetching
Timer t = new Timer();
t.schedule(new TimerTask() {
@Override
@ -43,7 +43,7 @@ public class CurrentUser extends User {
Platform.runLater(() -> setState(CurrentUser.State.VALID));
t.cancel();
}
}, 3000);
}, 500);
}

View file

@ -4,12 +4,13 @@ import fr.insa.clavardator.chat.ChatHistory;
import fr.insa.clavardator.chat.Message;
import fr.insa.clavardator.network.PeerConnection;
import fr.insa.clavardator.util.ErrorCallback;
import org.jetbrains.annotations.NotNull;
import java.io.EOFException;
import java.net.InetAddress;
import java.net.Socket;
public class PeerUser extends User {
public class PeerUser extends User implements Comparable<PeerUser> {
private State state = State.DISCONNECTED;
private transient PeerConnection connection;
@ -29,8 +30,7 @@ public class PeerUser extends User {
*/
public void connect(InetAddress ipAddr, UserConnectedCallback callback, ErrorCallback errorCallback) {
// Connect to the peer
pcs.firePropertyChange("state", state, State.CONNECTING);
state = State.CONNECTING;
setState(State.CONNECTING);
connection = new PeerConnection(ipAddr, (thisConnection) -> {
init(thisConnection, callback, errorCallback);
}, e -> {
@ -47,8 +47,7 @@ public class PeerUser extends User {
* @param errorCallback The function to call on socket error
*/
public void connect(Socket socket, UserConnectedCallback callback, ErrorCallback errorCallback) {
pcs.firePropertyChange("state", state, State.CONNECTING);
state = State.CONNECTING;
setState(State.CONNECTING);
connection = new PeerConnection(socket);
init(connection, callback, errorCallback);
@ -78,10 +77,7 @@ public class PeerUser extends User {
disconnect();
errorCallback.onError(e);
});
// Update state to CONNECTED
pcs.firePropertyChange("state", state, State.CONNECTED);
state = State.CONNECTED;
setState(State.CONNECTED);
}, e -> {
disconnect();
@ -117,8 +113,7 @@ public class PeerUser extends User {
connection.close();
connection = null;
}
pcs.firePropertyChange("state", state, State.DISCONNECTED);
state = State.DISCONNECTED;
setState(State.DISCONNECTED);
}
@ -131,12 +126,34 @@ public class PeerUser extends User {
return history;
}
private void setState(State state) {
pcs.firePropertyChange("state", this.state, state);
this.state = state;
}
public State getState() {
return state;
}
public boolean isActive() {
return state == State.CONNECTED;
}
enum State {
CONNECTING,
CONNECTED,
DISCONNECTED,
}
@Override
public int compareTo(@NotNull PeerUser peerUser) {
if (peerUser.isActive() && !this.isActive()) {
return 1;
} else if (!peerUser.isActive() && this.isActive()) {
return -1;
}
return getUsername().compareTo(peerUser.getUsername());
}
public interface UserConnectedCallback {
void onUserConnected();

View file

@ -6,7 +6,7 @@ import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.Serializable;
public class User implements Serializable, Comparable<User> {
public class User implements Serializable {
private String username;
public int id;
@ -44,15 +44,4 @@ public class User implements Serializable, Comparable<User> {
pcs.firePropertyChange("username", this.username, newUsername);
this.username = newUsername;
}
@Override
public int compareTo(@NotNull User o) {
if ((o instanceof ActiveUser) && !(this instanceof ActiveUser)) {
return 1;
} else if (!(o instanceof ActiveUser) && (this instanceof ActiveUser)) {
return -1;
}
return username.compareTo(o.username);
}
}

View file

@ -6,13 +6,14 @@ import fr.insa.clavardator.util.ErrorCallback;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;
public class UserList {
private Map<Integer, PeerUser> inactiveUsers;
private Map<Integer, PeerUser> activeUsers;
private final Map<Integer, PeerUser> inactiveUsers = new HashMap<>();
private final Map<Integer, PeerUser> activeUsers = new HashMap<>();
private final ArrayList<UserConnectionCallback> userConnectionObservers = new ArrayList<>();
private final ArrayList<UserDisconnectionCallback> userDisconnectionObservers = new ArrayList<>();
@ -33,13 +34,10 @@ public class UserList {
netDiscoverer.discoverActiveUsers("CLAVARDATOR_BROADCAST", (ipAddr, data) -> {
int id = getIdFromIp(ipAddr);
PeerUser user = inactiveUsers.get(id);
if (user == null) {
user = new PeerUser(id, "");
}
PeerUser finalUser = user;
user.connect(ipAddr, () -> {
notifyConnectionObservers(finalUser);
}, errorCallback);
@ -47,12 +45,12 @@ public class UserList {
}, errorCallback);
}
private int getIdFromIp(InetAddress ipAddr) {
return ipAddr.hashCode();
public void startDiscoveryListening() {
netDiscoverer.startDiscoveryListening("", null, Throwable::printStackTrace);
}
public void addActiveUser(ActiveUser user) {
private int getIdFromIp(InetAddress ipAddr) {
return ipAddr.hashCode();
}
public void addActiveUsersObserver(UserConnectionCallback connectionCallback, UserDisconnectionCallback disconnectionCallback) {
@ -109,11 +107,11 @@ public class UserList {
return inactiveUsers;
}
interface UserConnectionCallback {
public interface UserConnectionCallback {
void onUserConnected(PeerUser user);
}
interface UserDisconnectionCallback {
public interface UserDisconnectionCallback {
void onUserDisconnected(PeerUser user);
}

View file

@ -15,7 +15,7 @@
<VBox>
<fx:include source="toolbar.fxml" fx:id="toolbar"/>
<HBox VBox.vgrow="ALWAYS">
<fx:include source="users/userList.fxml" fx:id="userList"/>
<fx:include source="users/userList.fxml" fx:id="list"/>
<fx:include source="chat/chat.fxml" fx:id="chat" HBox.hgrow="ALWAYS"/>
</HBox>
</VBox>

View file

@ -16,6 +16,6 @@
</graphic>
</JFXButton>
</HBox>
<ListView fx:id="userList" VBox.vgrow="ALWAYS"/>
<ListView fx:id="peerUserListView" VBox.vgrow="ALWAYS"/>
</VBox>
</AnchorPane>