Compare commits

...

3 Commits

Author SHA1 Message Date
Jan-Peter Klein
1bc256c6d5 implemented input validation 2025-03-04 15:23:01 +01:00
Jan-Peter Klein
f3581c5acf implemented NetworkSettingsJson 2024-07-02 16:39:33 +02:00
Jan-Peter Klein
506827ffef preferences network mockup 2024-06-28 12:59:12 +02:00
12 changed files with 323 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
package org.cryptomator.common.settings;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class NetworkSettings {
public final ObjectProperty<ProxyMode> mode;
public final StringProperty httpProxy;
public final IntegerProperty httpPort;
public final BooleanProperty samePortProxyForHttpHttps;
public final StringProperty httpsProxy;
public final IntegerProperty httpsPort;
NetworkSettings(NetworkSettingsJson json){
this.mode = new SimpleObjectProperty<>(this, "mode", json.mode);
this.httpProxy = new SimpleStringProperty(this, "httpProxy", json.httpProxy);
this.httpPort = new SimpleIntegerProperty(this, "httpPort", json.httpPort);
this.samePortProxyForHttpHttps = new SimpleBooleanProperty(this, "samePortProxyForHttpHttps", json.samePortProxyForHttpHttps);
this.httpsProxy = new SimpleStringProperty(this, "httpsProxy", json.httpsProxy);
this.httpsPort = new SimpleIntegerProperty(this, "httpsPort", json.httpsPort);
}
NetworkSettingsJson serialized(){
var json = new NetworkSettingsJson();
json.mode = mode.get();
json.httpProxy = httpProxy.get();
json.httpPort = httpPort.get();
json.samePortProxyForHttpHttps = samePortProxyForHttpHttps.get();
json.httpsProxy = httpsProxy.get();
json.httpsPort = httpsPort.get();
return json;
}
Observable[] observables() {
return new Observable[]{mode, httpProxy, httpPort,httpsProxy,httpsPort};
}
public enum ProxyMode {
NO,
SYSTEM,
MANUAL
}
}

View File

@@ -0,0 +1,29 @@
package org.cryptomator.common.settings;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
class NetworkSettingsJson {
@JsonProperty("mode")
NetworkSettings.ProxyMode mode = NetworkSettings.ProxyMode.NO;
@JsonProperty("httpProxy")
String httpProxy;
@JsonProperty("httpPort")
int httpPort;
@JsonProperty("samePortProxyForHttpHttps")
boolean samePortProxyForHttpHttps;
@JsonProperty(value = "httpsProxy")
String httpsProxy;
@JsonProperty("httpsPort")
int httpsPort;
}

View File

@@ -47,6 +47,7 @@ public class Settings {
static final boolean DEFAULT_SHOW_MINIMIZE_BUTTON = false;
public static final Instant DEFAULT_TIMESTAMP = Instant.parse("2000-01-01T00:00:00Z");
public final ObservableList<VaultSettings> directories;
public final ObjectProperty<NetworkSettings> networkSettings;
public final BooleanProperty askedForUpdateCheck;
public final BooleanProperty checkForUpdates;
public final BooleanProperty startHidden;
@@ -84,6 +85,7 @@ public class Settings {
*/
Settings(SettingsJson json) {
this.directories = FXCollections.observableArrayList(VaultSettings::observables);
this.networkSettings = new SimpleObjectProperty<>(this, "networkSettings", new NetworkSettings(json.networkSettings));
this.askedForUpdateCheck = new SimpleBooleanProperty(this, "askedForUpdateCheck", json.askedForUpdateCheck);
this.checkForUpdates = new SimpleBooleanProperty(this, "checkForUpdates", json.checkForUpdatesEnabled);
this.startHidden = new SimpleBooleanProperty(this, "startHidden", json.startHidden);
@@ -111,6 +113,12 @@ public class Settings {
migrateLegacySettings(json);
directories.addListener(this::somethingChanged);
networkSettings.get().mode.addListener(this::somethingChanged);
networkSettings.get().httpProxy.addListener(this::somethingChanged);
networkSettings.get().httpPort.addListener(this::somethingChanged);
networkSettings.get().samePortProxyForHttpHttps.addListener(this::somethingChanged);
networkSettings.get().httpsProxy.addListener(this::somethingChanged);
networkSettings.get().httpsPort.addListener(this::somethingChanged);
askedForUpdateCheck.addListener(this::somethingChanged);
checkForUpdates.addListener(this::somethingChanged);
startHidden.addListener(this::somethingChanged);
@@ -165,6 +173,7 @@ public class Settings {
SettingsJson serialized() {
var json = new SettingsJson();
json.directories = directories.stream().map(VaultSettings::serialized).toList();
json.networkSettings = networkSettings.get().serialized();
json.askedForUpdateCheck = askedForUpdateCheck.get();
json.checkForUpdatesEnabled = checkForUpdates.get();
json.startHidden = startHidden.get();

View File

@@ -15,6 +15,9 @@ class SettingsJson {
@JsonProperty("directories")
List<VaultSettingsJson> directories = List.of();
@JsonProperty("networkSettings")
NetworkSettingsJson networkSettings = new NetworkSettingsJson();
@JsonProperty("writtenByVersion")
String writtenByVersion;

View File

@@ -40,6 +40,7 @@ public enum FontAwesome5Icon {
LOCK("\uF023"), //
LOCK_OPEN("\uF3C1"), //
MAGIC("\uF0D0"), //
NETWORK("\uF6FF"), //
PENCIL("\uF303"), //
PLUS("\uF067"), //
PRINT("\uF02F"), //

View File

@@ -0,0 +1,159 @@
package org.cryptomator.ui.preferences;
import org.cryptomator.common.settings.NetworkSettings;
import org.cryptomator.common.settings.Settings;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.controls.NumericTextField;
import javax.inject.Inject;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.StringProperty;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TextField;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
@PreferencesScoped
public class NetworkPreferencesController implements FxController {
private final Settings settings;
public ToggleGroup proxyToggleGroup;
public RadioButton noProxyBtn;
public RadioButton systemSettingsBtn;
public RadioButton manualProxyBtn;
public VBox manualProxyBox;
public HBox httpsProxyBox;
@FXML
public TextField httpProxy;
public NumericTextField httpPort;
public CheckBox samePortProxyForHttpHttps;
public TextField httpsProxy;
public NumericTextField httpsPort;
@Inject
NetworkPreferencesController(Settings settings) {
this.settings = settings;
}
@FXML
public void initialize() {
initializeTextFieldWithValidation(httpProxy, settings.networkSettings.get().httpProxy);
initializeTextFieldWithValidation(httpPort, settings.networkSettings.get().httpPort);
initializeTextFieldWithValidation(httpsProxy, settings.networkSettings.get().httpsProxy);
initializeTextFieldWithValidation(httpsPort, settings.networkSettings.get().httpsPort);
samePortProxyForHttpHttps.selectedProperty().bindBidirectional(settings.networkSettings.get().samePortProxyForHttpHttps);
httpsProxyBox.setDisable(settings.networkSettings.get().samePortProxyForHttpHttps.get());
samePortProxyForHttpHttps.selectedProperty().addListener((_, _, newValue) -> {
httpsProxyBox.setDisable(newValue);
});
switch (settings.networkSettings.get().mode.get()) {
case NO -> {
proxyToggleGroup.selectToggle(noProxyBtn);
manualProxyBox.setDisable(true);
}
case SYSTEM -> {
proxyToggleGroup.selectToggle(systemSettingsBtn);
manualProxyBox.setDisable(true);
}
case MANUAL -> {
proxyToggleGroup.selectToggle(manualProxyBtn);
manualProxyBox.setDisable(false);
}
}
proxyToggleGroup.selectedToggleProperty().addListener((_, _, newValue) -> {
if (newValue != null) {
manualProxyBox.setDisable(!newValue.equals(manualProxyBtn));
if (newValue.equals(noProxyBtn)) {
settings.networkSettings.get().mode.set(NetworkSettings.ProxyMode.NO);
} else if (newValue.equals(systemSettingsBtn)) {
settings.networkSettings.get().mode.set(NetworkSettings.ProxyMode.SYSTEM);
} else if (newValue.equals(manualProxyBtn)) {
settings.networkSettings.get().mode.set(NetworkSettings.ProxyMode.MANUAL);
}
}
});
}
private void initializeTextFieldWithValidation(TextField textField, StringProperty property) {
textField.setText(property.get());
textField.textProperty().addListener((_, _, newValue) -> {
if (isValidHttpProxy(newValue)) {
textField.setStyle("-fx-border-color: green; -fx-border-width: 2px; -fx-border-radius: 3px; -fx-padding: 3px;");
} else {
textField.setStyle("-fx-border-color: red; -fx-border-width: 2px; -fx-border-radius: 3px; -fx-padding: 3px;");
}
});
textField.focusedProperty().addListener((_, oldValue, newValue) -> {
if (!newValue) {
String text = textField.getText();
if (isValidHttpProxy(text)) {
property.set(text);
textField.setStyle("");
}
}
});
}
private boolean isValidHttpProxy(String text) {
if (text == null || text.isEmpty()) {
return false;
}
String regex = "^([\\w.-]+)$";
return text.matches(regex);
}
private void initializeTextFieldWithValidation(TextField textField, IntegerProperty property) {
textField.setText(String.valueOf(property.get()));
textField.textProperty().addListener((observable, oldValue, newValue) -> {
if (isValidPort(newValue)) {
textField.setStyle("-fx-border-color: green; -fx-border-width: 2px; -fx-border-radius: 3px; -fx-padding: 3px;");
} else {
textField.setStyle("-fx-border-color: red; -fx-border-width: 2px; -fx-border-radius: 3px; -fx-padding: 3px;");
}
});
textField.focusedProperty().addListener((_, _, newValue) -> {
if (!newValue) {
String text = textField.getText();
if (isValidPort(text)) {
property.set(Integer.parseInt(text));
textField.setText(removeLeadingZeros(textField.getText()));
textField.setStyle("");
}
}
});
}
public static String removeLeadingZeros(String str) {
if (str == null || str.isEmpty()) {
return str;
}
return str.replaceFirst("^0+(?!$)", "");
}
private boolean isValidPort(String text) {
if (text == null || text.isEmpty()) {
return false;
}
try {
int value = Integer.parseInt(text);
return value >= 1024 && value <= 8888;
} catch (NumberFormatException e) {
return false;
}
}
}

View File

@@ -31,6 +31,7 @@ public class PreferencesController implements FxController {
public Tab updatesTab;
public Tab contributeTab;
public Tab aboutTab;
public Tab networkTab;
@Inject
public PreferencesController(Environment env, @PreferencesWindow Stage window, ObjectProperty<SelectedPreferencesTab> selectedTabProperty, UpdateChecker updateChecker) {
@@ -63,6 +64,7 @@ public class PreferencesController implements FxController {
case UPDATES -> updatesTab;
case CONTRIBUTE -> contributeTab;
case ABOUT -> aboutTab;
case NETWORK -> networkTab;
case ANY -> updateAvailable.get() ? updatesTab : generalTab;
};
}

View File

@@ -89,4 +89,9 @@ abstract class PreferencesModule {
@FxControllerKey(AboutController.class)
abstract FxController bindAboutController(AboutController controller);
@Binds
@IntoMap
@FxControllerKey(NetworkPreferencesController.class)
abstract FxController bindNetworkPreferencesController(NetworkPreferencesController controller);
}

View File

@@ -35,4 +35,8 @@ public enum SelectedPreferencesTab {
* Show about tab
*/
ABOUT,
/**
* Show network tab
*/
NETWORK
}

View File

@@ -46,6 +46,14 @@
<fx:include source="preferences_updates.fxml"/>
</content>
</Tab>
<Tab fx:id="network" id="NETWORK" text="%preferences.network">
<graphic>
<FontAwesome5IconView glyph="NETWORK"/>
</graphic>
<content>
<fx:include source="preferences_network.fxml"/>
</content>
</Tab>
<Tab fx:id="contributeTab" id="CONTRIBUTE" text="%preferences.contribute">
<graphic>
<FontAwesome5IconView glyph="HEART"/>

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.CheckBox?>
<?import javafx.scene.control.ToggleGroup?>
<?import org.cryptomator.ui.controls.NumericTextField?>
<VBox xmlns:fx="http://javafx.com/fxml"
xmlns="http://javafx.com/javafx"
fx:controller="org.cryptomator.ui.preferences.NetworkPreferencesController"
spacing="24">
<fx:define>
<ToggleGroup fx:id="proxyToggleGroup"/>
</fx:define>
<padding>
<Insets topRightBottomLeft="24"/>
</padding>
<VBox>
<Label text="Configure Proxy Access to the Internet"/>
<RadioButton fx:id="noProxyBtn" text="No Proxy" toggleGroup="${proxyToggleGroup}" />
<RadioButton fx:id="systemSettingsBtn" text="Use system proxy settings" toggleGroup="${proxyToggleGroup}" />
<RadioButton fx:id="manualProxyBtn" text="Manual proxy configuration" toggleGroup="${proxyToggleGroup}" />
<VBox fx:id="manualProxyBox">
<padding>
<Insets left="24"/>
</padding>
<HBox spacing="12" alignment="CENTER_LEFT">
<Label text="HTTP Proxy" minWidth="90" />
<TextField fx:id="httpProxy" HBox.hgrow="ALWAYS" />
<Label text="Port"/>
<NumericTextField fx:id="httpPort" prefWidth="60" promptText="0" />
</HBox>
<CheckBox fx:id="samePortProxyForHttpHttps" text="Also use this proxy for HTTPS"/>
<HBox fx:id="httpsProxyBox" spacing="12" alignment="CENTER_LEFT">
<Label text="HTTPS Proxy" minWidth="90"/>
<TextField fx:id="httpsProxy" HBox.hgrow="ALWAYS" />
<Label text="Port"/>
<NumericTextField fx:id="httpsPort" prefWidth="60" promptText="0"/>
</HBox>
</VBox>
</VBox>
</VBox>

View File

@@ -328,6 +328,8 @@ preferences.updates.lastUpdateCheck.daysAgo=%s days ago
preferences.updates.lastUpdateCheck.hoursAgo=%s hours ago
preferences.updates.checkFailed=Looking for updates failed. Please check your internet connection or try again later.
preferences.updates.upToDate=Cryptomator is up-to-date.
## Network
preferences.network=Network
## Contribution
preferences.contribute=Support Us