Browse Source

- Added settings (references #88)
- Added dependency EasyBind to UI
- Using property bindings instead of listeners in lots of places of the UI now

Sebastian Stenzel 9 years ago
parent
commit
94b8726379
21 changed files with 505 additions and 500 deletions
  1. 0 2
      main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/MacOsXWebDavMounter.java
  2. 7 0
      main/pom.xml
  3. 6 0
      main/ui/pom.xml
  4. 1 1
      main/ui/src/main/java/org/cryptomator/ui/MainApplication.java
  5. 8 6
      main/ui/src/main/java/org/cryptomator/ui/controllers/AbstractFXMLViewController.java
  6. 28 52
      main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java
  7. 17 38
      main/ui/src/main/java/org/cryptomator/ui/controllers/InitializeController.java
  8. 24 26
      main/ui/src/main/java/org/cryptomator/ui/controllers/MacWarningsController.java
  9. 123 132
      main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java
  10. 59 0
      main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java
  11. 89 142
      main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java
  12. 47 35
      main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java
  13. 3 24
      main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java
  14. 3 3
      main/ui/src/main/java/org/cryptomator/ui/controls/SecPasswordField.java
  15. 14 18
      main/ui/src/main/java/org/cryptomator/ui/model/Vault.java
  16. 9 16
      main/ui/src/main/java/org/cryptomator/ui/util/FXThreads.java
  17. 25 0
      main/ui/src/main/java/org/cryptomator/ui/util/Listeners.java
  18. 2 2
      main/ui/src/main/resources/fxml/main.fxml
  19. 35 0
      main/ui/src/main/resources/fxml/settings.fxml
  20. 1 2
      main/ui/src/main/resources/fxml/welcome.fxml
  21. 4 1
      main/ui/src/main/resources/localization.properties

+ 0 - 2
main/frontend-webdav/src/main/java/org/cryptomator/frontend/webdav/mount/MacOsXWebDavMounter.java

@@ -72,8 +72,6 @@ final class MacOsXWebDavMounter implements WebDavMounterStrategy {
 			String activateAppleScript = String.format("tell application \"Finder\" to activate \"%s\"", volumeIdentifier);
 			String ejectAppleScript = String.format("tell application \"Finder\" to if \"%s\" exists then eject \"%s\"", volumeIdentifier, volumeIdentifier);
 
-			System.err.println("open: " + openAppleScript + "\nactivate: " + activateAppleScript + "\neject: " + ejectAppleScript);
-
 			this.revealCommand = new ProcessBuilder("/usr/bin/osascript", "-e", openAppleScript, "-e", activateAppleScript);
 			this.unmountCommand = new ProcessBuilder("/usr/bin/osascript", "-e", ejectAppleScript);
 		}

+ 7 - 0
main/pom.xml

@@ -170,6 +170,13 @@
 				<artifactId>commons-httpclient</artifactId>
 				<version>${commons-httpclient.version}</version>
 			</dependency>
+			
+			<!-- EasyBind -->
+			<dependency>
+				<groupId>org.fxmisc.easybind</groupId>
+				<artifactId>easybind</artifactId>
+				<version>1.0.3</version>
+			</dependency>	
 
 			<!-- Guava -->
 			<dependency>

+ 6 - 0
main/ui/pom.xml

@@ -50,6 +50,12 @@
 			<groupId>org.cryptomator</groupId>
 			<artifactId>frontend-webdav</artifactId>
 		</dependency>
+		
+		<!-- EasyBind -->
+		<dependency>
+			<groupId>org.fxmisc.easybind</groupId>
+			<artifactId>easybind</artifactId>
+		</dependency>	
 
 		<!-- JSON -->
 		<dependency>

+ 1 - 1
main/ui/src/main/java/org/cryptomator/ui/MainApplication.java

@@ -70,7 +70,7 @@ public class MainApplication extends Application {
 		mainCtrl.initStage(primaryStage);
 
 		final ResourceBundle rb = ResourceBundle.getBundle("localization");
-		primaryStage.setTitle(rb.getString("app.name"));
+		primaryStage.titleProperty().bind(mainCtrl.windowTitle());
 		primaryStage.setResizable(false);
 		primaryStage.getIcons().add(new Image(MainApplication.class.getResourceAsStream("/window_icon.png")));
 		primaryStage.show();

+ 8 - 6
main/ui/src/main/java/org/cryptomator/ui/controllers/AbstractFXMLViewController.java

@@ -11,6 +11,9 @@ package org.cryptomator.ui.controllers;
 import java.io.IOException;
 import java.net.URL;
 import java.util.ResourceBundle;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.cryptomator.common.LazyInitializer;
 
 import javafx.fxml.FXMLLoader;
 import javafx.fxml.Initializable;
@@ -23,7 +26,7 @@ import javafx.stage.Stage;
  */
 abstract class AbstractFXMLViewController implements Initializable {
 
-	private Parent fxmlRoot;
+	private final AtomicReference<Parent> fxmlRoot = new AtomicReference<>();
 
 	/**
 	 * URL from #initialize(URL, ResourceBundle)
@@ -80,16 +83,15 @@ abstract class AbstractFXMLViewController implements Initializable {
 	 * 
 	 * @return Parent view element.
 	 */
-	protected final synchronized Parent loadFxml() {
-		if (fxmlRoot == null) {
+	protected final Parent loadFxml() {
+		return LazyInitializer.initializeLazily(fxmlRoot, () -> {
 			final FXMLLoader loader = createFxmlLoader();
 			try {
-				fxmlRoot = loader.load();
+				return loader.load();
 			} catch (IOException e) {
 				throw new IllegalStateException("Could not load FXML file from location: " + loader.getLocation(), e);
 			}
-		}
-		return fxmlRoot;
+		});
 	}
 
 	/**

+ 28 - 52
main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java

@@ -9,6 +9,7 @@
 package org.cryptomator.ui.controllers;
 
 import java.io.IOException;
+import java.io.UncheckedIOException;
 import java.net.URL;
 import java.util.Optional;
 import java.util.ResourceBundle;
@@ -17,6 +18,7 @@ import javax.inject.Inject;
 import javax.inject.Singleton;
 
 import org.cryptomator.crypto.engine.InvalidPassphraseException;
+import org.cryptomator.crypto.engine.UnsupportedVaultFormatException;
 import org.cryptomator.ui.controls.SecPasswordField;
 import org.cryptomator.ui.model.Vault;
 import org.slf4j.Logger;
@@ -24,7 +26,9 @@ import org.slf4j.LoggerFactory;
 
 import javafx.application.Application;
 import javafx.application.Platform;
-import javafx.beans.value.ObservableValue;
+import javafx.beans.binding.BooleanBinding;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
 import javafx.event.ActionEvent;
 import javafx.fxml.FXML;
 import javafx.scene.control.Button;
@@ -36,8 +40,14 @@ public class ChangePasswordController extends AbstractFXMLViewController {
 
 	private static final Logger LOG = LoggerFactory.getLogger(ChangePasswordController.class);
 
+	private final Application app;
+	final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
 	private Optional<ChangePasswordListener> listener = Optional.empty();
-	private Vault vault;
+
+	@Inject
+	public ChangePasswordController(Application app) {
+		this.app = app;
+	}
 
 	@FXML
 	private SecPasswordField oldPasswordField;
@@ -57,12 +67,12 @@ public class ChangePasswordController extends AbstractFXMLViewController {
 	@FXML
 	private Hyperlink downloadsPageLink;
 
-	private final Application app;
-
-	@Inject
-	public ChangePasswordController(Application app) {
-		super();
-		this.app = app;
+	@Override
+	public void initialize() {
+		BooleanBinding oldPasswordIsEmpty = oldPasswordField.textProperty().isEmpty();
+		BooleanBinding newPasswordIsEmpty = newPasswordField.textProperty().isEmpty();
+		BooleanBinding passwordsDiffer = newPasswordField.textProperty().isNotEqualTo(retypePasswordField.textProperty());
+		changePasswordButton.disableProperty().bind(oldPasswordIsEmpty.or(newPasswordIsEmpty.or(passwordsDiffer)));
 	}
 
 	@Override
@@ -75,24 +85,6 @@ public class ChangePasswordController extends AbstractFXMLViewController {
 		return ResourceBundle.getBundle("localization");
 	}
 
-	@Override
-	public void initialize() {
-		oldPasswordField.textProperty().addListener(this::passwordFieldsDidChange);
-		newPasswordField.textProperty().addListener(this::passwordFieldsDidChange);
-		retypePasswordField.textProperty().addListener(this::passwordFieldsDidChange);
-	}
-
-	// ****************************************
-	// Password fields
-	// ****************************************
-
-	private void passwordFieldsDidChange(ObservableValue<? extends String> property, String oldValue, String newValue) {
-		boolean oldPasswordIsEmpty = oldPasswordField.getText().isEmpty();
-		boolean newPasswordIsEmpty = newPasswordField.getText().isEmpty();
-		boolean passwordsAreEqual = newPasswordField.getText().equals(retypePasswordField.getText());
-		changePasswordButton.setDisable(oldPasswordIsEmpty || newPasswordIsEmpty || !passwordsAreEqual);
-	}
-
 	// ****************************************
 	// Downloads link
 	// ****************************************
@@ -110,31 +102,23 @@ public class ChangePasswordController extends AbstractFXMLViewController {
 	private void didClickChangePasswordButton(ActionEvent event) {
 		downloadsPageLink.setVisible(false);
 		try {
-			vault.changePassphrase(oldPasswordField.getCharacters(), newPasswordField.getCharacters());
+			vault.get().changePassphrase(oldPasswordField.getCharacters(), newPasswordField.getCharacters());
 			messageText.setText(resourceBundle.getString("changePassword.infoMessage.success"));
 			listener.ifPresent(this::invokeListenerLater);
 		} catch (InvalidPassphraseException e) {
 			messageText.setText(resourceBundle.getString("changePassword.errorMessage.wrongPassword"));
-			newPasswordField.swipe();
-			retypePasswordField.swipe();
 			Platform.runLater(oldPasswordField::requestFocus);
-			return;
-		} catch (IOException ex) {
+		} catch (UncheckedIOException | IOException ex) {
 			messageText.setText(resourceBundle.getString("changePassword.errorMessage.decryptionFailed"));
 			LOG.error("Decryption failed for technical reasons.", ex);
-			newPasswordField.swipe();
-			retypePasswordField.swipe();
-			return;
-			// } catch (UnsupportedVaultException e) {
-			// downloadsPageLink.setVisible(true);
-			// if (e.isVaultOlderThanSoftware()) {
-			// messageText.setText(resourceBundle.getString("changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " ");
-			// } else if (e.isSoftwareOlderThanVault()) {
-			// messageText.setText(resourceBundle.getString("changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault") + " ");
-			// }
-			// newPasswordField.swipe();
-			// retypePasswordField.swipe();
-			// return;
+		} catch (UnsupportedVaultFormatException e) {
+			downloadsPageLink.setVisible(true);
+			LOG.warn("Unable to unlock vault: " + e.getMessage());
+			if (e.isVaultOlderThanSoftware()) {
+				messageText.setText(resourceBundle.getString("unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " ");
+			} else if (e.isSoftwareOlderThanVault()) {
+				messageText.setText(resourceBundle.getString("unlock.errorMessage.unsupportedVersion.softwareOlderThanVault") + " ");
+			}
 		} finally {
 			oldPasswordField.swipe();
 			newPasswordField.swipe();
@@ -144,14 +128,6 @@ public class ChangePasswordController extends AbstractFXMLViewController {
 
 	/* Getter/Setter */
 
-	public Vault getVault() {
-		return vault;
-	}
-
-	public void setVault(Vault vault) {
-		this.vault = vault;
-	}
-
 	public ChangePasswordListener getListener() {
 		return listener.orElse(null);
 	}

+ 17 - 38
main/ui/src/main/java/org/cryptomator/ui/controllers/InitializeController.java

@@ -9,6 +9,7 @@
 package org.cryptomator.ui.controllers;
 
 import java.io.IOException;
+import java.io.UncheckedIOException;
 import java.net.URL;
 import java.nio.file.FileAlreadyExistsException;
 import java.util.Optional;
@@ -23,7 +24,9 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import javafx.application.Platform;
-import javafx.beans.value.ObservableValue;
+import javafx.beans.binding.BooleanBinding;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
 import javafx.event.ActionEvent;
 import javafx.fxml.FXML;
 import javafx.scene.control.Button;
@@ -34,9 +37,13 @@ public class InitializeController extends AbstractFXMLViewController {
 
 	private static final Logger LOG = LoggerFactory.getLogger(InitializeController.class);
 
-	private Vault vault;
+	final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
 	private Optional<InitializationListener> listener = Optional.empty();
 
+	@Inject
+	public InitializeController() {
+	}
+
 	@FXML
 	private SecPasswordField passwordField;
 
@@ -49,8 +56,11 @@ public class InitializeController extends AbstractFXMLViewController {
 	@FXML
 	private Label messageLabel;
 
-	@Inject
-	public InitializeController() {
+	@Override
+	public void initialize() {
+		BooleanBinding passwordIsEmpty = passwordField.textProperty().isEmpty();
+		BooleanBinding passwordsDiffer = passwordField.textProperty().isNotEqualTo(retypePasswordField.textProperty());
+		okButton.disableProperty().bind(passwordIsEmpty.or(passwordsDiffer));
 	}
 
 	@Override
@@ -63,60 +73,29 @@ public class InitializeController extends AbstractFXMLViewController {
 		return ResourceBundle.getBundle("localization");
 	}
 
-	@Override
-	public void initialize() {
-		passwordField.textProperty().addListener(this::passwordFieldsDidChange);
-		retypePasswordField.textProperty().addListener(this::passwordFieldsDidChange);
-	}
-
-	// ****************************************
-	// Password fields
-	// ****************************************
-
-	private void passwordFieldsDidChange(ObservableValue<? extends String> property, String oldValue, String newValue) {
-		boolean passwordIsEmpty = passwordField.getText().isEmpty();
-		boolean passwordsAreEqual = passwordField.getText().equals(retypePasswordField.getText());
-		okButton.setDisable(passwordIsEmpty || !passwordsAreEqual);
-	}
-
 	// ****************************************
 	// OK button
 	// ****************************************
 
 	@FXML
 	protected void initializeVault(ActionEvent event) {
-		setControlsDisabled(true);
 		final CharSequence passphrase = passwordField.getCharacters();
 		try {
-			vault.create(passphrase);
+			vault.get().create(passphrase);
 			listener.ifPresent(this::invokeListenerLater);
 		} catch (FileAlreadyExistsException ex) {
 			messageLabel.setText(resourceBundle.getString("initialize.messageLabel.alreadyInitialized"));
-		} catch (IOException ex) {
+		} catch (UncheckedIOException | IOException ex) {
 			LOG.error("I/O Exception", ex);
+			messageLabel.setText(resourceBundle.getString("initialize.messageLabel.initializationFailed"));
 		} finally {
-			setControlsDisabled(false);
 			passwordField.swipe();
 			retypePasswordField.swipe();
 		}
 	}
 
-	private void setControlsDisabled(boolean disable) {
-		passwordField.setDisable(disable);
-		retypePasswordField.setDisable(disable);
-		okButton.setDisable(disable);
-	}
-
 	/* Getter/Setter */
 
-	public Vault getVault() {
-		return vault;
-	}
-
-	public void setVault(Vault vault) {
-		this.vault = vault;
-	}
-
 	public InitializationListener getListener() {
 		return listener.orElse(null);
 	}

+ 24 - 26
main/ui/src/main/java/org/cryptomator/ui/controllers/MacWarningsController.java

@@ -19,8 +19,10 @@ import org.cryptomator.ui.model.Vault;
 import javafx.application.Application;
 import javafx.beans.Observable;
 import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.ReadOnlyStringWrapper;
 import javafx.beans.property.SimpleBooleanProperty;
+import javafx.beans.property.SimpleObjectProperty;
 import javafx.beans.value.ChangeListener;
 import javafx.beans.value.ObservableValue;
 import javafx.beans.value.WeakChangeListener;
@@ -38,33 +40,23 @@ import javafx.util.StringConverter;
 
 public class MacWarningsController extends AbstractFXMLViewController {
 
-	@FXML
-	private ListView<Warning> warningsList;
-
-	@FXML
-	private Button whitelistButton;
-
 	private final Application application;
 	private final ObservableList<Warning> warnings = FXCollections.observableArrayList();
 	private final ListChangeListener<String> unauthenticatedResourcesChangeListener = this::unauthenticatedResourcesDidChange;
 	private final ChangeListener<Boolean> stageVisibilityChangeListener = this::windowVisibilityDidChange;
+	final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
 	private Stage stage;
-	private Vault vault;
 
 	@Inject
 	public MacWarningsController(Application application) {
 		this.application = application;
 	}
 
-	@Override
-	protected URL getFxmlResourceUrl() {
-		return getClass().getResource("/fxml/mac_warnings.fxml");
-	}
+	@FXML
+	private ListView<Warning> warningsList;
 
-	@Override
-	protected ResourceBundle getFxmlResourceBundle() {
-		return ResourceBundle.getBundle("localization");
-	}
+	@FXML
+	private Button whitelistButton;
 
 	@Override
 	public void initialize() {
@@ -85,6 +77,16 @@ public class MacWarningsController extends AbstractFXMLViewController {
 		}));
 	}
 
+	@Override
+	protected URL getFxmlResourceUrl() {
+		return getClass().getResource("/fxml/mac_warnings.fxml");
+	}
+
+	@Override
+	protected ResourceBundle getFxmlResourceBundle() {
+		return ResourceBundle.getBundle("localization");
+	}
+
 	@Override
 	public void initStage(Stage stage) {
 		super.initStage(stage);
@@ -96,8 +98,8 @@ public class MacWarningsController extends AbstractFXMLViewController {
 	private void didClickWhitelistButton(ActionEvent event) {
 		warnings.filtered(w -> w.isSelected()).stream().forEach(w -> {
 			final String resourceToBeWhitelisted = w.getName();
-			vault.getWhitelistedResourcesWithInvalidMac().add(resourceToBeWhitelisted);
-			vault.getNamesOfResourcesWithInvalidMac().remove(resourceToBeWhitelisted);
+			vault.get().getWhitelistedResourcesWithInvalidMac().add(resourceToBeWhitelisted);
+			vault.get().getNamesOfResourcesWithInvalidMac().remove(resourceToBeWhitelisted);
 		});
 		warnings.removeIf(w -> w.isSelected());
 	}
@@ -125,12 +127,12 @@ public class MacWarningsController extends AbstractFXMLViewController {
 
 	private void windowVisibilityDidChange(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
 		if (Boolean.TRUE.equals(newValue)) {
-			stage.setTitle(String.format(resourceBundle.getString("macWarnings.windowTitle"), vault.getName()));
-			warnings.addAll(vault.getNamesOfResourcesWithInvalidMac().stream().map(Warning::new).collect(Collectors.toList()));
-			vault.getNamesOfResourcesWithInvalidMac().addListener(this.unauthenticatedResourcesChangeListener);
+			stage.setTitle(String.format(resourceBundle.getString("macWarnings.windowTitle"), vault.get().getName()));
+			warnings.addAll(vault.get().getNamesOfResourcesWithInvalidMac().stream().map(Warning::new).collect(Collectors.toList()));
+			vault.get().getNamesOfResourcesWithInvalidMac().addListener(this.unauthenticatedResourcesChangeListener);
 		} else {
-			vault.getNamesOfResourcesWithInvalidMac().clear();
-			vault.getNamesOfResourcesWithInvalidMac().removeListener(this.unauthenticatedResourcesChangeListener);
+			vault.get().getNamesOfResourcesWithInvalidMac().clear();
+			vault.get().getNamesOfResourcesWithInvalidMac().removeListener(this.unauthenticatedResourcesChangeListener);
 		}
 	}
 
@@ -138,10 +140,6 @@ public class MacWarningsController extends AbstractFXMLViewController {
 		whitelistButton.setDisable(warnings.filtered(w -> w.isSelected()).isEmpty());
 	}
 
-	public void setVault(Vault vault) {
-		this.vault = vault;
-	}
-
 	private class Warning {
 
 		private final ReadOnlyStringWrapper name = new ReadOnlyStringWrapper();

+ 123 - 132
main/ui/src/main/java/org/cryptomator/ui/controllers/MainController.java

@@ -13,10 +13,10 @@ import java.io.IOException;
 import java.net.URL;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.ResourceBundle;
-import java.util.stream.Collectors;
 
 import javax.inject.Inject;
 import javax.inject.Provider;
@@ -26,13 +26,19 @@ import org.cryptomator.ui.controls.DirectoryListCell;
 import org.cryptomator.ui.model.Vault;
 import org.cryptomator.ui.model.VaultFactory;
 import org.cryptomator.ui.settings.Settings;
+import org.cryptomator.ui.util.Listeners;
+import org.fxmisc.easybind.EasyBind;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import dagger.Lazy;
 import javafx.application.Platform;
+import javafx.beans.binding.Binding;
+import javafx.beans.binding.BooleanBinding;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.beans.value.ObservableValue;
 import javafx.collections.FXCollections;
-import javafx.collections.ListChangeListener;
 import javafx.collections.ObservableList;
 import javafx.event.ActionEvent;
 import javafx.fxml.FXML;
@@ -47,13 +53,42 @@ import javafx.scene.layout.HBox;
 import javafx.scene.layout.Pane;
 import javafx.stage.FileChooser;
 import javafx.stage.Stage;
-import javafx.stage.WindowEvent;
 
 @Singleton
 public class MainController extends AbstractFXMLViewController {
 
 	private static final Logger LOG = LoggerFactory.getLogger(MainController.class);
 
+	private final VaultFactory vaultFactoy;
+	private final Lazy<WelcomeController> welcomeController;
+	private final Lazy<InitializeController> initializeController;
+	private final Lazy<UnlockController> unlockController;
+	private final Provider<UnlockedController> unlockedControllerProvider;
+	private final Lazy<ChangePasswordController> changePasswordController;
+	private final Lazy<SettingsController> settingsController;
+	private final ObjectProperty<AbstractFXMLViewController> activeController = new SimpleObjectProperty<>();
+	private final ObservableList<Vault> vaults;
+	private final ObjectProperty<Vault> selectedVault = new SimpleObjectProperty<>();
+	private final Binding<Boolean> isSelectedVaultUnlocked = EasyBind.select(selectedVault).selectObject(Vault::unlockedProperty);
+	private final BooleanBinding isShowingSettings;
+	private final Map<Vault, UnlockedController> unlockedVaults = new HashMap<>();
+
+	@Inject
+	public MainController(Settings settings, VaultFactory vaultFactoy, Lazy<WelcomeController> welcomeController, Lazy<InitializeController> initializeController, Lazy<UnlockController> unlockController,
+			Provider<UnlockedController> unlockedControllerProvider, Lazy<ChangePasswordController> changePasswordController, Lazy<SettingsController> settingsController) {
+		this.vaultFactoy = vaultFactoy;
+		this.welcomeController = welcomeController;
+		this.initializeController = initializeController;
+		this.unlockController = unlockController;
+		this.unlockedControllerProvider = unlockedControllerProvider;
+		this.changePasswordController = changePasswordController;
+		this.settingsController = settingsController;
+		this.vaults = FXCollections.observableList(settings.getDirectories());
+
+		// derived bindings:
+		this.isShowingSettings = activeController.isEqualTo(settingsController.get());
+	}
+
 	private Stage stage;
 
 	@FXML
@@ -75,27 +110,23 @@ public class MainController extends AbstractFXMLViewController {
 	private Button removeVaultButton;
 
 	@FXML
-	private Pane contentPane;
+	private ToggleButton settingsButton;
 
-	private final Settings settings;
-	private final VaultFactory vaultFactoy;
-	private final Lazy<WelcomeController> welcomeController;
-	private final Lazy<InitializeController> initializeController;
-	private final Lazy<UnlockController> unlockController;
-	private final Provider<UnlockedController> unlockedController;
-	private final Lazy<ChangePasswordController> changePasswordController;
+	@FXML
+	private Pane contentPane;
 
-	@Inject
-	public MainController(Settings settings, VaultFactory vaultFactoy, Lazy<WelcomeController> welcomeController, Lazy<InitializeController> initializeController, Lazy<UnlockController> unlockController,
-			Provider<UnlockedController> unlockedController, Lazy<ChangePasswordController> changePasswordController) {
-		super();
-		this.settings = settings;
-		this.vaultFactoy = vaultFactoy;
-		this.welcomeController = welcomeController;
-		this.initializeController = initializeController;
-		this.unlockController = unlockController;
-		this.unlockedController = unlockedController;
-		this.changePasswordController = changePasswordController;
+	@Override
+	public void initialize() {
+		activeController.addListener(this::activeControllerDidChange);
+		activeController.set(welcomeController.get());
+		vaultList.setItems(vaults);
+		vaultList.setCellFactory(this::createDirecoryListCell);
+		selectedVault.addListener(this::selectedVaultDidChange);
+		selectedVault.bind(vaultList.getSelectionModel().selectedItemProperty());
+		addVaultContextMenu.showingProperty().addListener(Listeners.withNewValue(addVaultButton::setSelected));
+		removeVaultButton.disableProperty().bind(selectedVault.isNull());
+		isShowingSettings.addListener(Listeners.withNewValue(settingsButton::setSelected));
+		isSelectedVaultUnlocked.addListener(Listeners.withNewValue(this::selectedVaultUnlockedDidChange));
 	}
 
 	@Override
@@ -108,14 +139,10 @@ public class MainController extends AbstractFXMLViewController {
 		return ResourceBundle.getBundle("localization");
 	}
 
-	@Override
-	public void initialize() {
-		final ObservableList<Vault> items = FXCollections.observableList(settings.getDirectories());
-		vaultList.setItems(items);
-		vaultList.setCellFactory(this::createDirecoryListCell);
-		vaultList.getSelectionModel().getSelectedItems().addListener(this::selectedVaultDidChange);
-		removeVaultButton.disableProperty().bind(vaultList.getSelectionModel().selectedItemProperty().isNull());
-		this.showWelcomeView();
+	private ListCell<Vault> createDirecoryListCell(ListView<Vault> param) {
+		final DirectoryListCell cell = new DirectoryListCell();
+		cell.setVaultContextMenu(vaultListCellContextMenu);
+		return cell;
 	}
 
 	@Override
@@ -133,16 +160,6 @@ public class MainController extends AbstractFXMLViewController {
 		}
 	}
 
-	@FXML
-	private void willShowAddVaultContextMenu(WindowEvent event) {
-		addVaultButton.setSelected(true);
-	}
-
-	@FXML
-	private void didHideAddVaultContextMenu(WindowEvent event) {
-		addVaultButton.setSelected(false);
-	}
-
 	@FXML
 	private void didClickCreateNewVault(ActionEvent event) {
 		final FileChooser fileChooser = new FileChooser();
@@ -201,143 +218,117 @@ public class MainController extends AbstractFXMLViewController {
 		}
 
 		final Vault vault = vaultFactoy.createVault(vaultPath);
-		if (!vaultList.getItems().contains(vault)) {
-			vaultList.getItems().add(vault);
+		if (!vaults.contains(vault)) {
+			vaults.add(vault);
 		}
 		vaultList.getSelectionModel().select(vault);
 	}
 
-	private ListCell<Vault> createDirecoryListCell(ListView<Vault> param) {
-		final DirectoryListCell cell = new DirectoryListCell();
-		cell.setVaultContextMenu(vaultListCellContextMenu);
-		return cell;
-	}
-
-	private void selectedVaultDidChange(ListChangeListener.Change<? extends Vault> change) {
-		final Vault selectedVault = vaultList.getSelectionModel().getSelectedItem();
-		if (selectedVault == null) {
-			stage.setTitle(resourceBundle.getString("app.name"));
-			showWelcomeView();
-		} else if (!Files.isDirectory(selectedVault.getPath())) {
-			Platform.runLater(() -> {
-				vaultList.getItems().remove(selectedVault);
-				vaultList.getSelectionModel().clearSelection();
-			});
-			stage.setTitle(resourceBundle.getString("app.name"));
-			showWelcomeView();
-		} else {
-			stage.setTitle(selectedVault.getName());
-			showVault(selectedVault);
-		}
-	}
-
 	@FXML
 	private void didClickRemoveSelectedEntry(ActionEvent e) {
-		final Vault selectedVault = vaultList.getSelectionModel().getSelectedItem();
-		vaultList.getItems().remove(selectedVault);
-		vaultList.getSelectionModel().clearSelection();
+		vaults.remove(selectedVault.get());
 	}
 
 	@FXML
 	private void didClickChangePassword(ActionEvent e) {
-		final Vault selectedVault = vaultList.getSelectionModel().getSelectedItem();
-		showChangePasswordView(selectedVault);
+		showChangePasswordView();
+	}
+
+	@FXML
+	private void didClickShowSettings(ActionEvent e) {
+		if (settingsController.get().equals(activeController.get())) {
+			activeController.set(welcomeController.get());
+		} else {
+			activeController.set(settingsController.get());
+		}
+		vaultList.getSelectionModel().clearSelection();
 	}
 
 	// ****************************************
-	// Subcontroller for right panel
+	// Bindings and Property Listeners
 	// ****************************************
 
-	private void showVault(Vault vault) {
-		if (vault.isUnlocked()) {
-			this.showUnlockedView(vault);
-		} else if (vault.isValidVaultDirectory()) {
-			this.showUnlockView(vault);
+	private void activeControllerDidChange(ObservableValue<? extends AbstractFXMLViewController> property, AbstractFXMLViewController oldValue, AbstractFXMLViewController newValue) {
+		final Parent root = newValue.loadFxml();
+		contentPane.getChildren().clear();
+		contentPane.getChildren().add(root);
+	}
+
+	private void selectedVaultDidChange(ObservableValue<? extends Vault> property, Vault oldValue, Vault newValue) {
+		if (newValue == null) {
+			return;
+		}
+		if (newValue.isUnlocked()) {
+			this.showUnlockedView(newValue);
+		} else if (newValue.isValidVaultDirectory()) {
+			this.showUnlockView();
 		} else {
-			this.showInitializeView(vault);
+			this.showInitializeView();
 		}
 	}
 
-	private void showWelcomeView() {
-		final Parent root = welcomeController.get().loadFxml();
-		contentPane.getChildren().clear();
-		contentPane.getChildren().add(root);
+	private void selectedVaultUnlockedDidChange(Boolean unlocked) {
+		if (unlocked == null) {
+			// no vault selected -> no-op
+		} else if (unlocked) {
+			Platform.setImplicitExit(false);
+			this.showUnlockedView(selectedVault.get());
+		} else {
+			this.showUnlockView();
+		}
+	}
+
+	public Binding<String> windowTitle() {
+		return EasyBind.monadic(selectedVault).map(Vault::getName).orElse(resourceBundle.getString("app.name"));
 	}
 
-	private void showInitializeView(Vault vault) {
+	// ****************************************
+	// Subcontroller for right panel
+	// ****************************************
+
+	private void showInitializeView() {
 		final InitializeController ctrl = initializeController.get();
-		final Parent root = ctrl.loadFxml();
-		contentPane.getChildren().clear();
-		contentPane.getChildren().add(root);
-		ctrl.setVault(vault);
+		ctrl.vault.bind(selectedVault);
 		ctrl.setListener(this::didInitialize);
+		activeController.set(ctrl);
 	}
 
 	public void didInitialize(InitializeController ctrl) {
-		showUnlockView(ctrl.getVault());
+		showUnlockView();
 	}
 
-	private void showUnlockView(Vault vault) {
+	private void showUnlockView() {
 		final UnlockController ctrl = unlockController.get();
-		final Parent root = ctrl.loadFxml();
-		contentPane.getChildren().clear();
-		contentPane.getChildren().add(root);
-		ctrl.setVault(vault);
-		ctrl.setListener(this::didUnlock);
-	}
-
-	public void didUnlock(UnlockController ctrl) {
-		showUnlockedView(ctrl.getVault());
-		Platform.setImplicitExit(false);
+		ctrl.vault.bind(selectedVault);
+		activeController.set(ctrl);
 	}
 
 	private void showUnlockedView(Vault vault) {
-		final UnlockedController ctrl = unlockedController.get();
-		final Parent root = ctrl.loadFxml();
-		contentPane.getChildren().clear();
-		contentPane.getChildren().add(root);
+		final UnlockedController ctrl = unlockedVaults.computeIfAbsent(vault, k -> {
+			return unlockedControllerProvider.get();
+		});
 		ctrl.setVault(vault);
 		ctrl.setListener(this::didLock);
+		activeController.set(ctrl);
 	}
 
 	public void didLock(UnlockedController ctrl) {
-		showUnlockView(ctrl.getVault());
-		if (getUnlockedVaults().isEmpty()) {
+		unlockedVaults.remove(ctrl.getVault());
+		showUnlockView();
+		if (vaults.stream().anyMatch(Vault::isUnlocked)) {
 			Platform.setImplicitExit(true);
 		}
 	}
 
-	private void showChangePasswordView(Vault vault) {
+	private void showChangePasswordView() {
 		final ChangePasswordController ctrl = changePasswordController.get();
-		final Parent root = ctrl.loadFxml();
-		contentPane.getChildren().clear();
-		contentPane.getChildren().add(root);
-		ctrl.setVault(vault);
+		ctrl.vault.bind(selectedVault);
 		ctrl.setListener(this::didChangePassword);
+		activeController.set(ctrl);
 	}
 
 	public void didChangePassword(ChangePasswordController ctrl) {
-		showUnlockView(ctrl.getVault());
-	}
-
-	/* Convenience */
-
-	public Collection<Vault> getVaults() {
-		return vaultList.getItems();
-	}
-
-	public Collection<Vault> getUnlockedVaults() {
-		return getVaults().stream().filter(d -> d.isUnlocked()).collect(Collectors.toSet());
-	}
-
-	/* public Getter/Setter */
-
-	public Stage getStage() {
-		return stage;
-	}
-
-	public void setStage(Stage stage) {
-		this.stage = stage;
+		showUnlockView();
 	}
 
 	/**

+ 59 - 0
main/ui/src/main/java/org/cryptomator/ui/controllers/SettingsController.java

@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2014, 2016 Sebastian Stenzel
+ * This file is licensed under the terms of the MIT license.
+ * See the LICENSE.txt file for more info.
+ * 
+ * Contributors:
+ *     Sebastian Stenzel - initial API and implementation
+ ******************************************************************************/
+package org.cryptomator.ui.controllers;
+
+import java.net.URL;
+import java.util.ResourceBundle;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.cryptomator.ui.settings.Settings;
+import org.fxmisc.easybind.EasyBind;
+
+import javafx.application.Application;
+import javafx.fxml.FXML;
+import javafx.scene.control.CheckBox;
+
+@Singleton
+public class SettingsController extends AbstractFXMLViewController {
+
+	private final Application app;
+	private final Settings settings;
+
+	@Inject
+	public SettingsController(Application app, Settings settings) {
+		this.app = app;
+		this.settings = settings;
+	}
+
+	@FXML
+	private CheckBox checkForUpdatesCheckbox;
+
+	@Override
+	public void initialize() {
+		checkForUpdatesCheckbox.setSelected(settings.isCheckForUpdatesEnabled());
+		EasyBind.subscribe(checkForUpdatesCheckbox.selectedProperty(), settings::setCheckForUpdatesEnabled);
+	}
+
+	@Override
+	protected URL getFxmlResourceUrl() {
+		return getClass().getResource("/fxml/settings.fxml");
+	}
+
+	@Override
+	protected ResourceBundle getFxmlResourceBundle() {
+		return ResourceBundle.getBundle("localization");
+	}
+
+	// private boolean areUpdatesManagedExternally() {
+	// return Boolean.parseBoolean(System.getProperty("cryptomator.updatesManagedExternally", "false"));
+	// }
+
+}

+ 89 - 142
main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockController.java

@@ -10,10 +10,8 @@ package org.cryptomator.ui.controllers;
 
 import java.net.URL;
 import java.util.Comparator;
-import java.util.Optional;
 import java.util.ResourceBundle;
 import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Future;
 
 import javax.inject.Inject;
 
@@ -26,12 +24,14 @@ import org.cryptomator.frontend.FrontendCreationFailedException;
 import org.cryptomator.frontend.webdav.mount.WindowsDriveLetters;
 import org.cryptomator.ui.controls.SecPasswordField;
 import org.cryptomator.ui.model.Vault;
-import org.cryptomator.ui.util.FXThreads;
+import org.fxmisc.easybind.EasyBind;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import javafx.application.Application;
 import javafx.application.Platform;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
 import javafx.beans.value.ChangeListener;
 import javafx.beans.value.ObservableValue;
 import javafx.event.ActionEvent;
@@ -51,8 +51,18 @@ public class UnlockController extends AbstractFXMLViewController {
 
 	private static final Logger LOG = LoggerFactory.getLogger(UnlockController.class);
 
-	private Optional<UnlockListener> listener = Optional.empty();
-	private Vault vault;
+	private final ExecutorService exec;
+	private final Application app;
+	private final WindowsDriveLetters driveLetters;
+	private final ChangeListener<Character> driveLetterChangeListener = this::winDriveLetterDidChange;
+	final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
+
+	@Inject
+	public UnlockController(Application app, ExecutorService exec, WindowsDriveLetters driveLetters) {
+		this.app = app;
+		this.exec = exec;
+		this.driveLetters = driveLetters;
+	}
 
 	@FXML
 	private SecPasswordField passwordField;
@@ -84,31 +94,8 @@ public class UnlockController extends AbstractFXMLViewController {
 	@FXML
 	private GridPane advancedOptions;
 
-	private final ExecutorService exec;
-	private final Application app;
-	private final WindowsDriveLetters driveLetters;
-	private final ChangeListener<Character> driveLetterChangeListener = this::winDriveLetterDidChange;
-
-	@Inject
-	public UnlockController(Application app, ExecutorService exec, WindowsDriveLetters driveLetters) {
-		this.app = app;
-		this.exec = exec;
-		this.driveLetters = driveLetters;
-	}
-
-	@Override
-	protected URL getFxmlResourceUrl() {
-		return getClass().getResource("/fxml/unlock.fxml");
-	}
-
-	@Override
-	protected ResourceBundle getFxmlResourceBundle() {
-		return ResourceBundle.getBundle("localization");
-	}
-
 	@Override
 	public void initialize() {
-		passwordField.textProperty().addListener(this::passwordFieldsDidChange);
 		advancedOptions.managedProperty().bind(advancedOptions.visibleProperty());
 		mountName.addEventFilter(KeyEvent.KEY_TYPED, this::filterAlphanumericKeyEvents);
 		mountName.textProperty().addListener(this::mountNameDidChange);
@@ -120,11 +107,26 @@ public class UnlockController extends AbstractFXMLViewController {
 			winDriveLetter.setVisible(false);
 			winDriveLetter.setManaged(false);
 		}
+		unlockButton.disableProperty().bind(passwordField.textProperty().isEmpty());
+
+		EasyBind.subscribe(vault, this::vaultChanged);
+	}
+
+	@Override
+	protected URL getFxmlResourceUrl() {
+		return getClass().getResource("/fxml/unlock.fxml");
 	}
 
-	private void resetView() {
+	@Override
+	protected ResourceBundle getFxmlResourceBundle() {
+		return ResourceBundle.getBundle("localization");
+	}
+
+	private void vaultChanged(Vault newVault) {
+		if (newVault == null) {
+			return;
+		}
 		passwordField.clear();
-		unlockButton.setDisable(true);
 		advancedOptions.setVisible(false);
 		advancedOptionsButton.setText(resourceBundle.getString("unlock.button.advancedOptions.show"));
 		progressIndicator.setVisible(false);
@@ -138,15 +140,10 @@ public class UnlockController extends AbstractFXMLViewController {
 		}
 		downloadsPageLink.setVisible(false);
 		messageText.setText(null);
-	}
-
-	// ****************************************
-	// Password field
-	// ****************************************
-
-	private void passwordFieldsDidChange(ObservableValue<? extends String> property, String oldValue, String newValue) {
-		boolean passwordIsEmpty = passwordField.getText().isEmpty();
-		unlockButton.setDisable(passwordIsEmpty);
+		mountName.setText(newVault.getMountName());
+		if (SystemUtils.IS_OS_WINDOWS) {
+			chooseSelectedDriveLetter();
+		}
 	}
 
 	// ****************************************
@@ -183,14 +180,14 @@ public class UnlockController extends AbstractFXMLViewController {
 	}
 
 	private void mountNameDidChange(ObservableValue<? extends String> property, String oldValue, String newValue) {
-		if (vault == null) {
+		if (vault.get() == null) {
 			return;
 		}
 		// newValue is guaranteed to be a-z0-9_, see #filterAlphanumericKeyEvents
 		if (newValue.isEmpty()) {
-			mountName.setText(vault.getMountName());
+			mountName.setText(vault.get().getMountName());
 		} else {
-			vault.setMountName(newValue);
+			vault.get().setMountName(newValue);
 		}
 	}
 
@@ -237,99 +234,19 @@ public class UnlockController extends AbstractFXMLViewController {
 	}
 
 	private void winDriveLetterDidChange(ObservableValue<? extends Character> property, Character oldValue, Character newValue) {
-		if (vault == null) {
+		if (vault.get() == null) {
 			return;
 		}
-		vault.setWinDriveLetter(newValue);
-	}
-
-	// ****************************************
-	// Unlock button
-	// ****************************************
-
-	@FXML
-	private void didClickUnlockButton(ActionEvent event) {
-		setControlsDisabled(true);
-		progressIndicator.setVisible(true);
-		downloadsPageLink.setVisible(false);
-		final CharSequence password = passwordField.getCharacters();
-		try {
-			vault.activateFrontend(password);
-			Future<Boolean> futureMount = exec.submit(vault::mount);
-			FXThreads.runOnMainThreadWhenFinished(exec, futureMount, this::unlockAndMountFinished);
-		} catch (InvalidPassphraseException e) {
-			setControlsDisabled(false);
-			progressIndicator.setVisible(false);
-			messageText.setText(resourceBundle.getString("unlock.errorMessage.wrongPassword"));
-			Platform.runLater(passwordField::requestFocus);
-		} catch (UnsupportedVaultFormatException e) {
-			setControlsDisabled(false);
-			progressIndicator.setVisible(false);
-			downloadsPageLink.setVisible(true);
-			LOG.warn("Unable to unlock vault: " + e.getMessage());
-			if (e.isVaultOlderThanSoftware()) {
-				messageText.setText(resourceBundle.getString("unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " ");
-			} else if (e.isSoftwareOlderThanVault()) {
-				messageText.setText(resourceBundle.getString("unlock.errorMessage.unsupportedVersion.softwareOlderThanVault") + " ");
-			}
-		} catch (FrontendCreationFailedException ex) {
-			setControlsDisabled(false);
-			progressIndicator.setVisible(false);
-			messageText.setText(resourceBundle.getString("unlock.errorMessage.decryptionFailed"));
-			LOG.error("Decryption failed for technical reasons.", ex);
-		} finally {
-			passwordField.swipe();
-		}
-	}
-
-	private void setControlsDisabled(boolean disable) {
-		passwordField.setDisable(disable);
-		mountName.setDisable(disable);
-		unlockButton.setDisable(disable);
-		advancedOptionsButton.setDisable(disable);
-	}
-
-	private void unlockAndMountFinished(boolean mountSuccess) {
-		progressIndicator.setVisible(false);
-		setControlsDisabled(false);
-		if (vault.isUnlocked() && !mountSuccess) {
-			exec.submit(vault::deactivateFrontend);
-		} else if (vault.isUnlocked() && mountSuccess) {
-			exec.submit(() -> {
-				try {
-					vault.reveal();
-				} catch (CommandFailedException e) {
-					LOG.error("Failed to reveal mounted vault", e);
-				}
-			});
-		}
-		if (mountSuccess) {
-			listener.ifPresent(this::invokeListenerLater);
-		}
-	}
-
-	/* Getter/Setter */
-
-	public Vault getVault() {
-		return vault;
-	}
-
-	public void setVault(Vault vault) {
-		this.resetView();
-		this.vault = vault;
-		this.mountName.setText(vault.getMountName());
-		if (SystemUtils.IS_OS_WINDOWS) {
-			chooseSelectedDriveLetter();
-		}
+		vault.get().setWinDriveLetter(newValue);
 	}
 
 	private void chooseSelectedDriveLetter() {
 		assert SystemUtils.IS_OS_WINDOWS;
 		// if the vault prefers a drive letter, that is currently occupied, this is our last chance to reset this:
-		if (driveLetters.getOccupiedDriveLetters().contains(vault.getWinDriveLetter())) {
-			vault.setWinDriveLetter(null);
+		if (driveLetters.getOccupiedDriveLetters().contains(vault.get().getWinDriveLetter())) {
+			vault.get().setWinDriveLetter(null);
 		}
-		final Character letter = vault.getWinDriveLetter();
+		final Character letter = vault.get().getWinDriveLetter();
 		if (letter == null) {
 			// first option is known to be 'auto-assign' due to #WinDriveLetterComparator.
 			this.winDriveLetter.getSelectionModel().selectFirst();
@@ -338,25 +255,55 @@ public class UnlockController extends AbstractFXMLViewController {
 		}
 	}
 
-	public UnlockListener getListener() {
-		return listener.orElse(null);
-	}
-
-	public void setListener(UnlockListener listener) {
-		this.listener = Optional.ofNullable(listener);
-	}
+	// ****************************************
+	// Unlock button
+	// ****************************************
 
-	/* callback */
+	@FXML
+	private void didClickUnlockButton(ActionEvent event) {
+		mountName.setDisable(true);
+		advancedOptionsButton.setDisable(true);
+		progressIndicator.setVisible(true);
+		downloadsPageLink.setVisible(false);
+		CharSequence password = passwordField.getCharacters();
+		exec.submit(() -> this.unlock(password));
 
-	private void invokeListenerLater(UnlockListener listener) {
-		Platform.runLater(() -> {
-			listener.didUnlock(this);
-		});
 	}
 
-	@FunctionalInterface
-	interface UnlockListener {
-		void didUnlock(UnlockController ctrl);
+	private void unlock(CharSequence password) {
+		try {
+			vault.get().activateFrontend(password);
+			vault.get().reveal();
+		} catch (InvalidPassphraseException e) {
+			Platform.runLater(() -> {
+				messageText.setText(resourceBundle.getString("unlock.errorMessage.wrongPassword"));
+				passwordField.requestFocus();
+			});
+		} catch (UnsupportedVaultFormatException e) {
+			LOG.warn("Unable to unlock vault: " + e.getMessage());
+			Platform.runLater(() -> {
+				downloadsPageLink.setVisible(true);
+				if (e.isVaultOlderThanSoftware()) {
+					messageText.setText(resourceBundle.getString("unlock.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " ");
+				} else if (e.isSoftwareOlderThanVault()) {
+					messageText.setText(resourceBundle.getString("unlock.errorMessage.unsupportedVersion.softwareOlderThanVault") + " ");
+				}
+			});
+		} catch (FrontendCreationFailedException e) {
+			LOG.error("Decryption failed for technical reasons.", e);
+			Platform.runLater(() -> {
+				messageText.setText(resourceBundle.getString("unlock.errorMessage.decryptionFailed"));
+			});
+		} catch (CommandFailedException e) {
+			LOG.error("Failed to reveal mounted vault", e);
+		} finally {
+			Platform.runLater(() -> {
+				passwordField.swipe();
+				mountName.setDisable(false);
+				advancedOptionsButton.setDisable(false);
+				progressIndicator.setVisible(false);
+			});
+		}
 	}
 
 }

+ 47 - 35
main/ui/src/main/java/org/cryptomator/ui/controllers/UnlockedController.java

@@ -19,11 +19,14 @@ import javax.inject.Provider;
 import org.cryptomator.frontend.CommandFailedException;
 import org.cryptomator.ui.model.Vault;
 import org.cryptomator.ui.util.ActiveWindowStyleSupport;
+import org.fxmisc.easybind.EasyBind;
 
 import javafx.animation.Animation;
 import javafx.animation.KeyFrame;
 import javafx.animation.Timeline;
 import javafx.application.Platform;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleObjectProperty;
 import javafx.collections.ListChangeListener;
 import javafx.event.ActionEvent;
 import javafx.event.EventHandler;
@@ -40,10 +43,22 @@ public class UnlockedController extends AbstractFXMLViewController {
 
 	private static final int IO_SAMPLING_STEPS = 100;
 	private static final double IO_SAMPLING_INTERVAL = 0.25;
+
+	private final Stage macWarningsWindow = new Stage();
+	private final MacWarningsController macWarningsController;
+	private final ExecutorService exec;
+	private final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
 	private Optional<LockListener> listener = Optional.empty();
-	private Vault vault;
 	private Timeline ioAnimation;
 
+	@Inject
+	public UnlockedController(Provider<MacWarningsController> macWarningsControllerProvider, ExecutorService exec) {
+		this.macWarningsController = macWarningsControllerProvider.get();
+		this.exec = exec;
+
+		macWarningsController.vault.bind(this.vault);
+	}
+
 	@FXML
 	private Label messageLabel;
 
@@ -53,14 +68,12 @@ public class UnlockedController extends AbstractFXMLViewController {
 	@FXML
 	private NumberAxis xAxis;
 
-	private final Stage macWarningsWindow = new Stage();
-	private final MacWarningsController macWarningsController;
-	private final ExecutorService exec;
+	@Override
+	public void initialize() {
+		macWarningsController.initStage(macWarningsWindow);
+		ActiveWindowStyleSupport.startObservingFocus(macWarningsWindow);
 
-	@Inject
-	public UnlockedController(Provider<MacWarningsController> macWarningsControllerProvider, ExecutorService exec) {
-		this.macWarningsController = macWarningsControllerProvider.get();
-		this.exec = exec;
+		EasyBind.subscribe(vault, this::vaultChanged);
 	}
 
 	@Override
@@ -73,17 +86,30 @@ public class UnlockedController extends AbstractFXMLViewController {
 		return ResourceBundle.getBundle("localization");
 	}
 
-	@Override
-	public void initialize() {
-		macWarningsController.initStage(macWarningsWindow);
-		ActiveWindowStyleSupport.startObservingFocus(macWarningsWindow);
+	private void vaultChanged(Vault newVault) {
+		if (newVault == null) {
+			return;
+		}
+
+		// listen to MAC warnings as long as this vault is unlocked:
+		final ListChangeListener<String> macWarningsListener = this::macWarningsDidChange;
+		newVault.getNamesOfResourcesWithInvalidMac().addListener(macWarningsListener);
+		newVault.unlockedProperty().addListener((observable, oldValue, newValue) -> {
+			if (Boolean.FALSE.equals(newValue)) {
+				newVault.getNamesOfResourcesWithInvalidMac().removeListener(macWarningsListener);
+			}
+		});
+
+		// (re)start throughput statistics:
+		stopIoSampling();
+		startIoSampling();
 	}
 
 	@FXML
 	private void didClickRevealVault(ActionEvent event) {
 		exec.submit(() -> {
 			try {
-				vault.reveal();
+				vault.get().reveal();
 			} catch (CommandFailedException e) {
 				Platform.runLater(() -> {
 					messageLabel.setText(resourceBundle.getString("unlocked.label.revealFailed"));
@@ -96,14 +122,14 @@ public class UnlockedController extends AbstractFXMLViewController {
 	private void didClickCloseVault(ActionEvent event) {
 		exec.submit(() -> {
 			try {
-				vault.unmount();
+				vault.get().unmount();
 			} catch (CommandFailedException e) {
 				Platform.runLater(() -> {
 					messageLabel.setText(resourceBundle.getString("unlocked.label.unmountFailed"));
 				});
 				return;
 			}
-			vault.deactivateFrontend();
+			vault.get().deactivateFrontend();
 			listener.ifPresent(this::invokeListenerLater);
 		});
 	}
@@ -166,7 +192,7 @@ public class UnlockedController extends AbstractFXMLViewController {
 		public void handle(ActionEvent event) {
 			step++;
 
-			final long decBytes = vault.pollBytesRead();
+			final long decBytes = vault.get().pollBytesRead();
 			final double smoothedDecBytes = oldDecBytes + SMOOTHING_FACTOR * (decBytes - oldDecBytes);
 			final double smoothedDecMb = smoothedDecBytes * BYTES_TO_MEGABYTES_FACTOR;
 			oldDecBytes = smoothedDecBytes > EFFECTIVELY_ZERO ? (long) smoothedDecBytes : 0l;
@@ -175,7 +201,7 @@ public class UnlockedController extends AbstractFXMLViewController {
 				decryptedBytes.getData().remove(0);
 			}
 
-			final long encBytes = vault.pollBytesWritten();
+			final long encBytes = vault.get().pollBytesWritten();
 			final double smoothedEncBytes = oldEncBytes + SMOOTHING_FACTOR * (encBytes - oldEncBytes);
 			final double smoothedEncMb = smoothedEncBytes * BYTES_TO_MEGABYTES_FACTOR;
 			oldEncBytes = smoothedEncBytes > EFFECTIVELY_ZERO ? (long) smoothedEncBytes : 0l;
@@ -192,27 +218,15 @@ public class UnlockedController extends AbstractFXMLViewController {
 	/* Getter/Setter */
 
 	public Vault getVault() {
-		return vault;
+		return this.vault.get();
 	}
 
 	public void setVault(Vault vault) {
-		this.vault = vault;
-		macWarningsController.setVault(vault);
-
-		// listen to MAC warnings as long as this vault is unlocked:
-		final ListChangeListener<String> macWarningsListener = this::macWarningsDidChange;
-		vault.getNamesOfResourcesWithInvalidMac().addListener(macWarningsListener);
-		vault.unlockedProperty().addListener((observable, oldValue, newValue) -> {
-			if (Boolean.FALSE.equals(newValue)) {
-				vault.getNamesOfResourcesWithInvalidMac().removeListener(macWarningsListener);
-			}
-		});
-
-		// (re)start throughput statistics:
-		stopIoSampling();
-		startIoSampling();
+		this.vault.set(vault);
 	}
 
+	/* callback */
+
 	public LockListener getListener() {
 		return listener.orElse(null);
 	}
@@ -221,8 +235,6 @@ public class UnlockedController extends AbstractFXMLViewController {
 		this.listener = Optional.ofNullable(listener);
 	}
 
-	/* callback */
-
 	private void invokeListenerLater(LockListener listener) {
 		Platform.runLater(() -> {
 			listener.didLock(this);

+ 3 - 24
main/ui/src/main/java/org/cryptomator/ui/controllers/WelcomeController.java

@@ -39,11 +39,9 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 
 import javafx.application.Application;
 import javafx.application.Platform;
-import javafx.beans.value.ObservableValue;
 import javafx.event.ActionEvent;
 import javafx.fxml.FXML;
 import javafx.scene.Node;
-import javafx.scene.control.CheckBox;
 import javafx.scene.control.Hyperlink;
 import javafx.scene.control.Label;
 import javafx.scene.control.ProgressIndicator;
@@ -61,9 +59,6 @@ public class WelcomeController extends AbstractFXMLViewController {
 	@FXML
 	private Node checkForUpdatesContainer;
 
-	@FXML
-	private CheckBox checkForUpdatesCheckbox;
-
 	@FXML
 	private Label checkForUpdatesStatus;
 
@@ -99,17 +94,11 @@ public class WelcomeController extends AbstractFXMLViewController {
 	@Override
 	public void initialize() {
 		botImageView.setImage(new Image(getClass().getResource("/bot_welcome.png").toString()));
-		checkForUpdatesCheckbox.setSelected(settings.isCheckForUpdatesEnabled());
-		checkForUpdatesCheckbox.selectedProperty().addListener(this::checkForUpdatesChanged);
 		if (areUpdatesManagedExternally()) {
 			checkForUpdatesContainer.setVisible(false);
 			checkForUpdatesContainer.setManaged(false);
-		} else {
-			checkForUpdatesCheckbox.setSelected(settings.isCheckForUpdatesEnabled());
-			checkForUpdatesCheckbox.selectedProperty().addListener(this::checkForUpdatesChanged);
-			if (settings.isCheckForUpdatesEnabled()) {
-				executor.execute(this::checkForUpdates);
-			}
+		} else if (settings.isCheckForUpdatesEnabled()) {
+			executor.execute(this::checkForUpdates);
 		}
 	}
 
@@ -117,14 +106,6 @@ public class WelcomeController extends AbstractFXMLViewController {
 	// Check for updates
 	// ****************************************
 
-	private void checkForUpdatesChanged(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
-		assert newValue != null;
-		settings.setCheckForUpdatesEnabled(newValue);
-		if (newValue) {
-			executor.execute(this::checkForUpdates);
-		}
-	}
-
 	private boolean areUpdatesManagedExternally() {
 		return Boolean.parseBoolean(System.getProperty("cryptomator.updatesManagedExternally", "false"));
 	}
@@ -134,7 +115,6 @@ public class WelcomeController extends AbstractFXMLViewController {
 			return;
 		}
 		Platform.runLater(() -> {
-			checkForUpdatesCheckbox.setVisible(false);
 			checkForUpdatesStatus.setText(resourceBundle.getString("welcome.checkForUpdates.label.currentlyChecking"));
 			checkForUpdatesIndicator.setVisible(true);
 		});
@@ -157,8 +137,7 @@ public class WelcomeController extends AbstractFXMLViewController {
 			// no error handling required. Maybe next time the version check is successful.
 		} finally {
 			Platform.runLater(() -> {
-				checkForUpdatesCheckbox.setVisible(true);
-				checkForUpdatesStatus.setText(resourceBundle.getString("welcome.checkForUpdates.label.checkboxLabel"));
+				checkForUpdatesStatus.setText("");
 				checkForUpdatesIndicator.setVisible(false);
 			});
 		}

+ 3 - 3
main/ui/src/main/java/org/cryptomator/ui/controls/SecPasswordField.java

@@ -26,7 +26,7 @@ public class SecPasswordField extends PasswordField {
 	 * Imagine the following example with <code>pass</code> being the password, <code>x</code> being the swipe char and <code>'</code> being the offset of the char array:
 	 * <ol>
 	 * <li>Append filling chars to the end of the password: <code>passxxxx'</code></li>
-	 * <li>Delete first 4 chars. Internal implementation will then copy the following chars to the position, where the deletion occured: <code>xxxx'xxxx</code></li>
+	 * <li>Delete first 4 chars. Internal implementation will then copy subsequent chars to the position, where the deletion occured: <code>xxxx'xxxx</code></li>
 	 * <li>Delete first 4 chars again, as we appended 4 chars in step 1: <code>'xxxxxx</code></li>
 	 * </ol>
 	 */
@@ -37,8 +37,8 @@ public class SecPasswordField extends PasswordField {
 		this.getContent().insert(pwLength, new String(fillingChars), false);
 		this.getContent().delete(0, pwLength, true);
 		this.getContent().delete(0, pwLength, true);
-		// previous text has now been overwritten. still we need to update the text to trigger some property bindings:
-		this.setText("");
+		// previous text has now been overwritten. but we still need to update the text to trigger some property bindings:
+		this.clear();
 	}
 
 }

+ 14 - 18
main/ui/src/main/java/org/cryptomator/ui/model/Vault.java

@@ -46,8 +46,9 @@ import org.cryptomator.ui.util.FXThreads;
 import com.google.common.collect.ImmutableMap;
 
 import dagger.Lazy;
-import javafx.beans.property.ObjectProperty;
-import javafx.beans.property.SimpleObjectProperty;
+import javafx.application.Platform;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.SimpleBooleanProperty;
 import javafx.collections.FXCollections;
 import javafx.collections.ObservableList;
 
@@ -62,7 +63,7 @@ public class Vault implements Serializable, CryptoFileSystemDelegate {
 	private final DeferredCloser closer;
 	private final ShorteningFileSystemFactory shorteningFileSystemFactory;
 	private final CryptoFileSystemFactory cryptoFileSystemFactory;
-	private final ObjectProperty<Boolean> unlocked = new SimpleObjectProperty<Boolean>(this, "unlocked", Boolean.FALSE);
+	private final BooleanProperty unlocked = new SimpleBooleanProperty();
 	private final ObservableList<String> namesOfResourcesWithInvalidMac = FXThreads.observableListOnMainThread(FXCollections.observableArrayList());
 	private final Set<String> whitelistedResourcesWithInvalidMac = new HashSet<>();
 	private final AtomicReference<FileSystem> nioFileSystem = new AtomicReference<>();
@@ -118,6 +119,7 @@ public class Vault implements Serializable, CryptoFileSystemDelegate {
 	}
 
 	public synchronized void activateFrontend(CharSequence passphrase) throws FrontendCreationFailedException {
+		boolean success = false;
 		try {
 			FileSystem fs = getNioFileSystem();
 			FileSystem shorteningFs = shorteningFileSystemFactory.get(fs);
@@ -127,16 +129,21 @@ public class Vault implements Serializable, CryptoFileSystemDelegate {
 			String contextPath = StringUtils.prependIfMissing(mountName, "/");
 			Frontend frontend = frontendFactory.get().create(statsFs, contextPath);
 			filesystemFrontend = closer.closeLater(frontend);
-			unlocked.set(true);
-		} catch (UncheckedIOException e) {
+			frontend.mount(getMountParams());
+			success = true;
+		} catch (UncheckedIOException | CommandFailedException e) {
 			throw new FrontendCreationFailedException(e);
+		} finally {
+			// unlocked is a observable property and should only be changed by the FX application thread
+			final boolean finalSuccess = success;
+			Platform.runLater(() -> unlocked.set(finalSuccess));
 		}
 	}
 
 	public synchronized void deactivateFrontend() {
 		filesystemFrontend.close();
 		statsFileSystem = Optional.empty();
-		unlocked.set(false);
+		Platform.runLater(() -> unlocked.set(false));
 	}
 
 	private Map<MountParam, Optional<String>> getMountParams() {
@@ -146,17 +153,6 @@ public class Vault implements Serializable, CryptoFileSystemDelegate {
 		);
 	}
 
-	public Boolean mount() {
-		try {
-			Optionals.ifPresent(filesystemFrontend.get(), f -> {
-				f.mount(getMountParams());
-			});
-			return true;
-		} catch (CommandFailedException e) {
-			return false;
-		}
-	}
-
 	public void reveal() throws CommandFailedException {
 		Optionals.ifPresent(filesystemFrontend.get(), Frontend::reveal);
 	}
@@ -213,7 +209,7 @@ public class Vault implements Serializable, CryptoFileSystemDelegate {
 		}
 	}
 
-	public ObjectProperty<Boolean> unlockedProperty() {
+	public BooleanProperty unlockedProperty() {
 		return unlocked;
 	}
 

+ 9 - 16
main/ui/src/main/java/org/cryptomator/ui/util/FXThreads.java

@@ -13,6 +13,7 @@ package org.cryptomator.ui.util;
 import java.util.Objects;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
+import java.util.function.Consumer;
 
 import javafx.application.Platform;
 import javafx.collections.ObservableList;
@@ -42,14 +43,14 @@ import javafx.collections.ObservableSet;
  * // when done, update text label:
  * runOnMainThreadWhenFinished(futureBookName, (bookName) -&gt; {
  * 	myLabel.setText(bookName);
- * }, (exception) -&gt; {
+ * } , (exception) -&gt; {
  * 	myLabel.setText(&quot;An exception occured: &quot; + exception.getMessage());
  * });
  * </pre>
  */
 public final class FXThreads {
 
-	private static final CallbackWhenTaskFailed DUMMY_EXCEPTION_CALLBACK = (e) -> {
+	private static final Consumer<Exception> DUMMY_EXCEPTION_CALLBACK = (e) -> {
 		// ignore.
 	};
 
@@ -65,11 +66,11 @@ public final class FXThreads {
 	 * });
 	 * </pre>
 	 * 
-	 * @param executor
+	 * @param executor The service to execute the background task on
 	 * @param task The task to wait for.
 	 * @param successCallback The action to perform, when the task finished.
 	 */
-	public static <T> void runOnMainThreadWhenFinished(ExecutorService executor, Future<T> task, CallbackWhenTaskFinished<T> successCallback) {
+	public static <T> void runOnMainThreadWhenFinished(ExecutorService executor, Future<T> task, Consumer<T> successCallback) {
 		runOnMainThreadWhenFinished(executor, task, successCallback, DUMMY_EXCEPTION_CALLBACK);
 	}
 
@@ -82,7 +83,7 @@ public final class FXThreads {
 	 * 
 	 * runOnMainThreadWhenFinished(futureBookNamePossiblyFailing, (bookName) -&gt; {
 	 * 	myLabel.setText(bookName);
-	 * }, (exception) -&gt; {
+	 * } , (exception) -&gt; {
 	 * 	myLabel.setText(&quot;An exception occured: &quot; + exception.getMessage());
 	 * });
 	 * </pre>
@@ -92,7 +93,7 @@ public final class FXThreads {
 	 * @param successCallback The action to perform, when the task finished.
 	 * @param exceptionCallback
 	 */
-	public static <T> void runOnMainThreadWhenFinished(ExecutorService executor, Future<T> task, CallbackWhenTaskFinished<T> successCallback, CallbackWhenTaskFailed exceptionCallback) {
+	public static <T> void runOnMainThreadWhenFinished(ExecutorService executor, Future<T> task, Consumer<T> successCallback, Consumer<Exception> exceptionCallback) {
 		Objects.requireNonNull(task, "task must not be null.");
 		Objects.requireNonNull(successCallback, "successCallback must not be null.");
 		Objects.requireNonNull(exceptionCallback, "exceptionCallback must not be null.");
@@ -100,24 +101,16 @@ public final class FXThreads {
 			try {
 				final T result = task.get();
 				Platform.runLater(() -> {
-					successCallback.taskFinished(result);
+					successCallback.accept(result);
 				});
 			} catch (Exception e) {
 				Platform.runLater(() -> {
-					exceptionCallback.taskFailed(e);
+					exceptionCallback.accept(e);
 				});
 			}
 		});
 	}
 
-	public interface CallbackWhenTaskFinished<T> {
-		void taskFinished(T result);
-	}
-
-	public interface CallbackWhenTaskFailed {
-		void taskFailed(Throwable t);
-	}
-
 	public static <E> ObservableSet<E> observableSetOnMainThread(ObservableSet<E> set) {
 		return new ObservableSetOnMainThread<E>(set);
 	}

+ 25 - 0
main/ui/src/main/java/org/cryptomator/ui/util/Listeners.java

@@ -0,0 +1,25 @@
+package org.cryptomator.ui.util;
+
+import java.util.function.Consumer;
+
+import javafx.beans.value.ChangeListener;
+import javafx.beans.value.ObservableValue;
+
+public final class Listeners {
+
+	private Listeners() {
+	}
+
+	/**
+	 * Invokes the given consumer with the new value as soon as a change is reported by an {@link ObservableValue}.
+	 * 
+	 * @param consumer The function to call with the changed value.
+	 * @return A listener that can i.e. be used in {@link ObservableValue#addListener(ChangeListener)}.
+	 */
+	public static <T> ChangeListener<T> withNewValue(Consumer<T> consumer) {
+		return (ObservableValue<? extends T> observable, T oldValue, T newValue) -> {
+			consumer.accept(newValue);
+		};
+	}
+
+}

+ 2 - 2
main/ui/src/main/resources/fxml/main.fxml

@@ -31,7 +31,7 @@
 				<MenuItem text="%main.directoryList.contextMenu.changePassword" onAction="#didClickChangePassword" />
 			</items>
 		</ContextMenu>
-		<ContextMenu fx:id="addVaultContextMenu" onShowing="#willShowAddVaultContextMenu" onHidden="#didHideAddVaultContextMenu">
+		<ContextMenu fx:id="addVaultContextMenu">
 			<items>
 				<MenuItem text="%main.addDirectory.contextMenu.new" onAction="#didClickCreateNewVault" />
 				<MenuItem text="%main.addDirectory.contextMenu.open" onAction="#didClickAddExistingVaults" />
@@ -49,7 +49,7 @@
 						<Button text="&#xf462;" fx:id="removeVaultButton" onAction="#didClickRemoveSelectedEntry" focusTraversable="false" cacheShape="true" cache="true"/>
 						<Pane HBox.hgrow="ALWAYS" styleClass="spacer" />
 						<!-- future use: -->
-						<Button text="&#xf43c;" disable="true" focusTraversable="false" cacheShape="true" cache="true"/>
+						<ToggleButton text="&#xf43c;" fx:id="settingsButton" onAction="#didClickShowSettings" focusTraversable="false" cacheShape="true" cache="true"/>
 					</items>
 				</ToolBar>
 			</children>

+ 35 - 0
main/ui/src/main/resources/fxml/settings.fxml

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Copyright (c) 2014 Sebastian Stenzel
+  This file is licensed under the terms of the MIT license.
+  See the LICENSE.txt file for more info.
+  
+  Contributors:
+      Sebastian Stenzel - initial API and implementation
+-->
+<?import java.net.URL?>
+<?import javafx.scene.layout.GridPane?>
+<?import javafx.geometry.Insets?>
+<?import javafx.scene.layout.ColumnConstraints?>
+<?import javafx.scene.control.Label?>
+<?import javafx.scene.control.CheckBox?>
+
+<GridPane vgap="12.0" hgap="12.0" prefWidth="400.0" xmlns:fx="http://javafx.com/fxml" cacheShape="true" cache="true">
+	<padding>
+		<Insets top="24.0" right="24.0" bottom="24.0" left="24.0" />
+	</padding>
+	
+	<columnConstraints>
+		<ColumnConstraints percentWidth="38.2"/>
+		<ColumnConstraints percentWidth="61.8"/>
+	</columnConstraints>
+
+	<children>
+		<!-- Row 0 -->
+		<Label GridPane.rowIndex="0" GridPane.columnIndex="0" text="%settings.checkForUpdates.label" cacheShape="true" cache="true" />
+		<CheckBox GridPane.rowIndex="0" GridPane.columnIndex="1" fx:id="checkForUpdatesCheckbox" cacheShape="true" cache="true" />
+	</children>
+</GridPane>
+
+
+					

+ 1 - 2
main/ui/src/main/resources/fxml/welcome.fxml

@@ -29,8 +29,7 @@
 			<Label alignment="CENTER" style="-fx-font-size: 1.5em;" text="%welcome.welcomeLabel" cacheShape="true" cache="true" />
 			<VBox fx:id="checkForUpdatesContainer" prefWidth="400.0" alignment="CENTER" spacing="5.0" cacheShape="true" cache="true">
 				<HBox alignment="CENTER" spacing="5.0" cacheShape="true" cache="true">
-					<CheckBox fx:id="checkForUpdatesCheckbox" cacheShape="true" cache="true" />
-					<Label fx:id="checkForUpdatesStatus" text="%welcome.checkForUpdates.label.checkboxLabel" cacheShape="true" cache="true" />
+					<Label fx:id="checkForUpdatesStatus" cacheShape="true" cache="true" />
 					<ProgressIndicator fx:id="checkForUpdatesIndicator" progress="-1" prefWidth="15.0" prefHeight="15.0" visible="false" cacheShape="true" cache="true" cacheHint="SPEED" />
 				</HBox>
 				<Hyperlink alignment="CENTER" fx:id="updateLink" onAction="#didClickUpdateLink" visible="false" cacheShape="true" cache="true" />

+ 4 - 1
main/ui/src/main/resources/localization.properties

@@ -18,7 +18,6 @@ main.addDirectory.contextMenu.open=Add existing vault
 # welcome.fxml
 welcome.welcomeLabel=Welcome to Cryptomator
 welcome.addButtonInstructionLabel=Start by adding a new vault
-welcome.checkForUpdates.label.checkboxLabel=Check for Updates
 welcome.checkForUpdates.label.currentlyChecking=Checking for Updates...
 welcome.newVersionMessage=Version %s can be downloaded. This is %s.
 
@@ -27,6 +26,7 @@ initialize.label.password=Password
 initialize.label.retypePassword=Retype password
 initialize.button.ok=Create vault
 initialize.messageLabel.alreadyInitialized=Vault already initialized
+initialize.messageLabel.initializationFailed=Could not initialize vault. See logfile for details.
 
 # unlock.fxml
 unlock.label.password=Password
@@ -71,6 +71,9 @@ macWarnings.message=Cryptomator detected potentially malicious corruptions in th
 macWarnings.moreInformationButton=Learn more
 macWarnings.whitelistButton=Decrypt selected anyway
 
+# settings.fxml
+settings.checkForUpdates.label=Check for updates
+
 # tray icon
 tray.menu.open=Open
 tray.menu.quit=Quit