Skip to content

Commit

Permalink
Feature/atlanta fx (#170)
Browse files Browse the repository at this point in the history
* Add new theme

* Add more UI changes

Replace fontawesome 4 by iconli + fontawesome 5.

Use Atlanta's toolbar instead of the custom one.

Add more dark mode fixes.

Fix a NPE when running without network connection.

* Fix colors in nicknames in the chat for dark mode

* Add ways to change themes

Also applied many fixes and removed old cruft.

* Improve UI

Fix "Reply" menu not working in forums.

Fix color for JOIN/PART events.

* Improve UI

Bigger default avatar in the chat.

Hyperlinks appear as the description instead of the URL. The URL is shown in the tooltip.

Improved the forum message header.

Improve the chat listview margins.

Markdown links are processed properly (instead of just the URL part).

* Add support to past own ID into forum threads

* Make chatroom only creatable if there's a room name and a topic

* Remove old CSS classes

* Fix tests
  • Loading branch information
zapek authored Nov 11, 2023
1 parent 26c8c90 commit fadaba9
Show file tree
Hide file tree
Showing 44 changed files with 1,121 additions and 981 deletions.
5 changes: 5 additions & 0 deletions app/src/main/java/io/xeres/app/net/protocol/PeerAddress.java
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,11 @@ public boolean isHostname()

private static boolean isInvalidIpAddress(String address)
{
if (address == null)
{
return true;
}

var octets = address.split("\\.");

if (octets.length != 4)
Expand Down
5 changes: 4 additions & 1 deletion ui/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,13 @@ dependencies {
implementation 'org.apache.commons:commons-lang3'
implementation "org.jsoup:jsoup:$jsoupVersion"
implementation "com.github.java-json-tools:json-patch:$jsonPatchVersion"
implementation 'de.jensd:fontawesomefx-fontawesome:4.7.0-9.1.2'
implementation 'com.github.sarxos:webcam-capture:0.3.12'
implementation "com.google.zxing:javase:$zxingVersion"
implementation "net.harawata:appdirs:$appDirsVersion"
implementation 'io.github.mkpaz:atlantafx-base:2.0.1'
implementation platform('org.kordamp.ikonli:ikonli-bom:12.3.1')
implementation 'org.kordamp.ikonli:ikonli-javafx'
implementation 'org.kordamp.ikonli:ikonli-fontawesome5-pack'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: "com.vaadin.external.google", module: "android-json"
}
Expand Down
19 changes: 19 additions & 0 deletions ui/src/main/java/io/xeres/ui/JavaFxApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@

import io.xeres.common.mui.MinimalUserInterface;
import io.xeres.ui.controller.MainWindowController;
import io.xeres.ui.support.theme.AppTheme;
import javafx.application.Application;
import javafx.application.HostServices;
import javafx.stage.Stage;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;

import java.lang.reflect.InvocationTargetException;
import java.util.Objects;
import java.util.prefs.Preferences;

public class JavaFxApplication extends Application
{
Expand Down Expand Up @@ -75,11 +78,27 @@ public void start(Stage primaryStage)
{
hostServices = getHostServices();

var preferences = Preferences.userRoot().node("Application");
var theme = preferences.get("Theme", AppTheme.PRIMER_LIGHT.getName());
setTheme(AppTheme.findByName(theme));

Objects.requireNonNull(springContext);
mainWindowController = springContext.getBean(MainWindowController.class);
springContext.publishEvent(new StageReadyEvent(primaryStage));
}

private static void setTheme(AppTheme appTheme)
{
try
{
Application.setUserAgentStylesheet(appTheme.getThemeClass().getDeclaredConstructor().newInstance().getUserAgentStylesheet());
}
catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e)
{
throw new RuntimeException(e);
}
}

@Override
public void stop()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@
import io.xeres.ui.support.util.UiUtils;
import javafx.application.Platform;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.control.Button;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.Label;
import javafx.scene.control.TabPane;
import javafx.scene.text.Text;
import net.rgielen.fxweaver.core.FxmlView;
import org.springframework.boot.info.BuildProperties;
import org.springframework.core.env.Environment;
Expand All @@ -46,7 +50,7 @@ public class AboutWindowController implements WindowController
private TabPane infoPane;

@FXML
private TextArea licenseTextArea;
private Text license;

@FXML
private Label version;
Expand Down Expand Up @@ -83,7 +87,7 @@ public void initialize() throws IOException
{
profile.setText("Profiles: " + String.join(", ", environment.getActiveProfiles()));
}
licenseTextArea.setText(UiUtils.getResourceFileAsString(getClass().getResourceAsStream("/LICENSE")));
license.setText(UiUtils.getResourceFileAsString(getClass().getResourceAsStream("/LICENSE")));

twemoji.setOnAction(event -> JavaFxApplication.openUrl("https://github.com/twitter/twemoji"));
twemojiLicense.setOnAction(event -> JavaFxApplication.openUrl("https://creativecommons.org/licenses/by/4.0/"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ public ChatRoomCreationWindowController(ChatClient chatClient, ResourceBundle bu
@Override
public void initialize()
{
roomName.textProperty().addListener(observable -> createButton.setDisable(roomName.getText().isBlank()));
topic.textProperty().addListener(observable -> createButton.setDisable(topic.getText().isBlank()));
roomName.textProperty().addListener(observable -> checkCreatable());
topic.textProperty().addListener(observable -> checkCreatable());

visibility.setItems(FXCollections.observableArrayList(bundle.getString("enum.roomtype.public"), bundle.getString("enum.roomtype.private")));
visibility.getSelectionModel().select(0);
Expand All @@ -83,4 +83,9 @@ public void initialize()
.subscribe());
cancelButton.setOnAction(UiUtils::closeWindow);
}

private void checkCreatable()
{
createButton.setDisable(roomName.getText().isBlank() || topic.getText().isBlank());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@

public class ChatUserCell extends ListCell<ChatRoomUser>
{
private static final ImageView defaultImage = new ImageView("/image/avatar_16.png");
private static final int DEFAULT_AVATAR_SIZE = 32;
private static final ImageView defaultImage = new ImageView("/image/avatar_" + DEFAULT_AVATAR_SIZE + ".png");

public ChatUserCell()
{
Expand All @@ -53,8 +54,8 @@ private ImageView getAvatarImage(ChatRoomUser item)
if (item.image() != null)
{
image = new ImageView(item.image().getImage());
image.setFitWidth(16.0);
image.setFitHeight(16.0);
image.setFitWidth(DEFAULT_AVATAR_SIZE);
image.setFitHeight(DEFAULT_AVATAR_SIZE);
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,8 @@

package io.xeres.ui.controller.chat;

import io.xeres.common.AppName;
import io.xeres.common.i18n.I18nUtils;
import io.xeres.common.message.chat.*;
import io.xeres.common.rest.location.RSIdResponse;
import io.xeres.common.rsid.Type;
import io.xeres.ui.client.ChatClient;
import io.xeres.ui.client.LocationClient;
import io.xeres.ui.client.ProfileClient;
Expand All @@ -35,12 +32,12 @@
import io.xeres.ui.support.markdown.MarkdownService;
import io.xeres.ui.support.tray.TrayService;
import io.xeres.ui.support.util.ImageUtils;
import io.xeres.ui.support.util.TextInputControlUtils;
import io.xeres.ui.support.util.UiUtils;
import io.xeres.ui.support.window.WindowManager;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.binding.Bindings;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
Expand All @@ -55,8 +52,6 @@
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.net.URI;
import java.net.URLEncoder;
import java.text.MessageFormat;
import java.time.Duration;
import java.time.Instant;
Expand All @@ -66,9 +61,7 @@
import java.util.function.Consumer;
import java.util.stream.Collectors;

import static io.xeres.common.dto.location.LocationConstants.OWN_LOCATION_ID;
import static io.xeres.common.message.chat.ChatConstants.TYPING_NOTIFICATION_DELAY;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.commons.lang3.ObjectUtils.isEmpty;
import static org.apache.commons.lang3.StringUtils.isNotBlank;

Expand Down Expand Up @@ -240,7 +233,7 @@ public void initialize() throws IOException
ae -> typingNotification.setText("")));

send.addEventHandler(KeyEvent.KEY_PRESSED, this::handleInputKeys);
send.setContextMenu(createChatInputContextMenu(send));
send.setContextMenu(TextInputControlUtils.createInputContextMenu(send, locationClient));

invite.setOnAction(event -> windowManager.openInvite(UiUtils.getWindow(event), selectedRoom.getId()));

Expand Down Expand Up @@ -640,70 +633,6 @@ private void setPreviewGroupVisibility(boolean visible)
previewGroup.setManaged(visible);
}

private ContextMenu createChatInputContextMenu(TextInputControl textInputControl)
{
var contextMenu = new ContextMenu();

contextMenu.getItems().addAll(createDefaultChatInputMenuItems(textInputControl));
var pasteId = new MenuItem(bundle.getString("chat.room.input.paste-id"));
pasteId.setOnAction(event -> appendOwnId(textInputControl));
contextMenu.getItems().addAll(new SeparatorMenuItem(), pasteId);
return contextMenu;
}

private void appendOwnId(TextInputControl textInputControl)
{
var rsIdResponse = locationClient.getRSId(OWN_LOCATION_ID, Type.CERTIFICATE);
rsIdResponse.subscribe(reply -> Platform.runLater(() -> textInputControl.appendText(buildRetroshareUrl(reply))));
}

private String buildRetroshareUrl(RSIdResponse rsIdResponse)
{
var uri = URI.create("retroshare://certificate?" +
"radix=" + URLEncoder.encode(rsIdResponse.rsId().replace("\n", ""), UTF_8) + // Removing the '\n' is in case this is a certificate which is sliced for presentation
"&amp;name=" + URLEncoder.encode(rsIdResponse.name(), UTF_8) +
"&amp;location=" + URLEncoder.encode(rsIdResponse.location(), UTF_8));
return "<a href=\"" + uri + "\">" + AppName.NAME + " Certificate (" + rsIdResponse.name() + ", @" + rsIdResponse.location() + ")</a>";
}

private List<MenuItem> createDefaultChatInputMenuItems(TextInputControl textInputControl)
{
var undo = new MenuItem(bundle.getString("chat.room.input.undo"));
undo.setOnAction(event -> textInputControl.undo());

var redo = new MenuItem(bundle.getString("chat.room.input.redo"));
redo.setOnAction(event -> textInputControl.redo());

var cut = new MenuItem(bundle.getString("chat.room.input.cut"));
cut.setOnAction(event -> textInputControl.cut());

var copy = new MenuItem(bundle.getString("chat.room.input.copy"));
copy.setOnAction(event -> textInputControl.copy());

var paste = new MenuItem(bundle.getString("chat.room.input.paste"));
paste.setOnAction(event -> textInputControl.paste());

var delete = new MenuItem(bundle.getString("chat.room.input.delete"));
delete.setOnAction(event -> textInputControl.deleteText(textInputControl.getSelection()));

var selectAll = new MenuItem(bundle.getString("chat.room.input.select-all"));
selectAll.setOnAction(event -> textInputControl.selectAll());

var emptySelection = Bindings.createBooleanBinding(() -> textInputControl.getSelection().getLength() == 0, textInputControl.selectionProperty());

cut.disableProperty().bind(emptySelection);
copy.disableProperty().bind(emptySelection);
delete.disableProperty().bind(emptySelection);

var canUndo = Bindings.createBooleanBinding(() -> !textInputControl.isUndoable(), textInputControl.undoableProperty());
var canRedo = Bindings.createBooleanBinding(() -> !textInputControl.isRedoable(), textInputControl.redoableProperty());

undo.disableProperty().bind(canUndo);
redo.disableProperty().bind(canRedo);

return List.of(undo, redo, cut, copy, paste, delete, new SeparatorMenuItem(), selectAll);
}

public void openInvite(long chatRoomId, ChatRoomInviteEvent event)
{
Platform.runLater(() -> UiUtils.alertConfirm(MessageFormat.format(bundle.getString("chat.room.invite.request"), event.getLocationId(), event.getRoomName(), event.getRoomTopic()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import io.xeres.common.message.forum.ForumMessage;
import io.xeres.common.rest.forum.PostRequest;
import io.xeres.ui.client.ForumClient;
import io.xeres.ui.client.LocationClient;
import io.xeres.ui.controller.WindowController;
import io.xeres.ui.custom.EditorView;
import io.xeres.ui.support.util.UiUtils;
Expand Down Expand Up @@ -55,10 +56,12 @@ public class ForumEditorViewController implements WindowController
private PostRequest postRequest;

private final ForumClient forumClient;
private final LocationClient locationClient;

public ForumEditorViewController(ForumClient forumClient)
public ForumEditorViewController(ForumClient forumClient, LocationClient locationClient)
{
this.forumClient = forumClient;
this.locationClient = locationClient;
}

@Override
Expand All @@ -67,6 +70,7 @@ public void initialize() throws IOException
Platform.runLater(() -> title.requestFocus());

editorView.lengthProperty.addListener((observable, oldValue, newValue) -> checkSendable((Integer) newValue));
editorView.setInputContextMenu(locationClient);
title.setOnKeyTyped(event -> checkSendable(editorView.lengthProperty.getValue()));

send.setOnAction(event -> postMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,28 @@
import io.xeres.common.rest.config.Capabilities;
import io.xeres.ui.client.ConfigClient;
import io.xeres.ui.model.settings.Settings;
import io.xeres.ui.support.theme.AppTheme;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
import net.rgielen.fxweaver.core.FxmlView;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.prefs.Preferences;

@Component
@FxmlView(value = "/view/settings/settings_general.fxml")
public class SettingsGeneralController implements SettingsController
{
@FXML
private ChoiceBox<AppTheme> themeSelector;

@FXML
private CheckBox autoStartEnabled;

Expand All @@ -48,9 +59,29 @@ public SettingsGeneralController(ConfigClient configClient)
this.configClient = configClient;
}

private static void changeTheme(ObservableValue<? extends AppTheme> observable, AppTheme oldValue, AppTheme newValue)
{
try
{
Application.setUserAgentStylesheet(newValue.getThemeClass().getDeclaredConstructor().newInstance().getUserAgentStylesheet());
var preferences = Preferences.userRoot().node("Application");
preferences.put("Theme", newValue.getName());
}
catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e)
{
throw new RuntimeException(e);
}
}

@Override
public void initialize()
{
themeSelector.getItems().addAll(Arrays.stream(AppTheme.values()).toList());
var preferences = Preferences.userRoot().node("Application");
var theme = preferences.get("Theme", AppTheme.PRIMER_LIGHT.getName());
themeSelector.getSelectionModel().select(AppTheme.findByName(theme));
themeSelector.getSelectionModel().selectedItemProperty().addListener(SettingsGeneralController::changeTheme);

configClient.getCapabilities()
.doOnSuccess(capabilities -> Platform.runLater(() -> {
if (capabilities.contains(Capabilities.AUTOSTART))
Expand Down
Loading

0 comments on commit fadaba9

Please sign in to comment.