From c6d90cdb17753f1171605120044c81ae20b456b4 Mon Sep 17 00:00:00 2001 From: Sebastian Stenzel Date: Tue, 13 Aug 2019 15:43:16 +0200 Subject: [PATCH] Added vault location presets to dialog (references #929) --- .../CreateNewVaultLocationController.java | 108 ++++++++++++------ .../ui/addvaultwizard/LocationPresets.java | 84 ++++++++++++++ main/ui/src/main/resources/css/dark_theme.css | 33 ++++++ .../ui/src/main/resources/css/light_theme.css | 33 ++++++ .../resources/fxml/addvault_new_location.fxml | 32 +++++- .../main/resources/i18n/strings.properties | 2 +- 6 files changed, 248 insertions(+), 44 deletions(-) create mode 100644 main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/LocationPresets.java diff --git a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java index 84dcfce61..438999cf1 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java @@ -2,44 +2,86 @@ package org.cryptomator.ui.addvaultwizard; import dagger.Lazy; import javafx.beans.binding.BooleanBinding; +import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; +import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.StringProperty; +import javafx.beans.value.ObservableValue; import javafx.fxml.FXML; import javafx.scene.Scene; +import javafx.scene.control.RadioButton; +import javafx.scene.control.Toggle; +import javafx.scene.control.ToggleGroup; import javafx.stage.DirectoryChooser; import javafx.stage.Stage; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.inject.Inject; import java.io.File; import java.io.IOException; +import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.FileAlreadyExistsException; import java.nio.file.Files; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ResourceBundle; @AddVaultWizardScoped public class CreateNewVaultLocationController implements FxController { + private static final Logger LOG = LoggerFactory.getLogger(CreateNewVaultLocationController.class); + private static final Path DEFAULT_CUSTOM_VAULT_PATH = Paths.get(System.getProperty("user.home")); + private final Stage window; private final Lazy previousScene; private final Lazy nextScene; + private final LocationPresets locationPresets; private final ObjectProperty vaultPath; private final BooleanBinding vaultPathIsNull; private final StringProperty vaultName; private final ResourceBundle resourceBundle; + private final BooleanProperty usePresetPath; + + private Path customVaultPath = DEFAULT_CUSTOM_VAULT_PATH; + public ToggleGroup predefinedLocationToggler; + public RadioButton dropboxRadioButton; + public RadioButton gdriveRadioButton; + public RadioButton customRadioButton; //TODO: add parameter for next window + @Inject - CreateNewVaultLocationController(@AddVaultWizard Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_NAME) Lazy previousScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD) Lazy nextScene, ObjectProperty vaultPath, StringProperty vaultName, ResourceBundle resourceBundle) { + CreateNewVaultLocationController(@AddVaultWizard Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_NAME) Lazy previousScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD) Lazy nextScene, LocationPresets locationPresets, ObjectProperty vaultPath, StringProperty vaultName, ResourceBundle resourceBundle) { this.window = window; this.previousScene = previousScene; this.nextScene = nextScene; + this.locationPresets = locationPresets; this.vaultPath = vaultPath; this.vaultName = vaultName; this.resourceBundle = resourceBundle; this.vaultPathIsNull = vaultPath.isNull(); + this.usePresetPath = new SimpleBooleanProperty(); + } + + @FXML + public void initialize() { + predefinedLocationToggler.selectedToggleProperty().addListener(this::togglePredefinedLocation); + usePresetPath.bind(predefinedLocationToggler.selectedToggleProperty().isNotEqualTo(customRadioButton)); + } + + private void togglePredefinedLocation(@SuppressWarnings("unused") ObservableValue observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) { + if (dropboxRadioButton.equals(newValue)) { + vaultPath.set(locationPresets.getDropboxLocation().resolve(vaultName.get())); + } else if (gdriveRadioButton.equals(newValue)) { + vaultPath.set(locationPresets.getGdriveLocation().resolve(vaultName.get())); + } else if (customRadioButton.equals(newValue)) { + vaultPath.set(customVaultPath.resolve(vaultName.get())); + } } @FXML @@ -49,58 +91,38 @@ public class CreateNewVaultLocationController implements FxController { @FXML public void next() { - //TODO: what if there exists already a vault? - if (hasFullAccessToLocation()) { - window.setScene(nextScene.get()); - } else { - //TODO error handling - } - } - - private boolean hasFullAccessToLocation() { try { - Path tmp = Files.createFile(vaultPath.get().resolve("tmp")); - Files.delete(tmp); - return true; + // check if we have write access AND the vaultPath doesn't already exist: + assert Files.isDirectory(vaultPath.get().getParent()); + Path createdDir = Files.createDirectory(vaultPath.get()); + Files.delete(createdDir); // assert: dir exists and is empty + window.setScene(nextScene.get()); + } catch (FileAlreadyExistsException e) { + LOG.warn("Can not use already existing vault path: {}", vaultPath.get()); + // TODO show specific error text "vault can not be created at this path because some object already exists" + } catch (NoSuchFileException | DirectoryNotEmptyException e) { + LOG.error("Failed to delete recently created directory.", e); + // TODO show generic error text for unexpected exception } catch (IOException e) { - return false; + LOG.warn("Can not create vault at path: {}", vaultPath.get()); + // TODO show generic error text for unexpected exception } } @FXML - public void chooseDirectory() { + public void chooseCustomVaultPath() { DirectoryChooser directoryChooser = new DirectoryChooser(); directoryChooser.setTitle(resourceBundle.getString("addvaultwizard.new.directoryPickerTitle")); - setInitialDirectory(directoryChooser); + directoryChooser.setInitialDirectory(customVaultPath.toFile()); final File file = directoryChooser.showDialog(window); if (file != null) { - vaultPath.setValue(file.toPath().toAbsolutePath()); + customVaultPath = file.toPath().toAbsolutePath(); + vaultPath.set(customVaultPath.resolve(vaultName.get())); } } - private void setInitialDirectory(DirectoryChooser chooser) { - File userHome; - try { - userHome = new File(System.getProperty("user.home")); - } catch (Exception e) { - userHome = null; - } - if (userHome != null) { - chooser.setInitialDirectory(userHome); - } - - } - /* Getter/Setter */ - public String getVaultName() { - return vaultName.get(); - } - - public StringProperty vaultNameProperty() { - return vaultName; - } - public Path getVaultPath() { return vaultPath.get(); } @@ -117,5 +139,15 @@ public class CreateNewVaultLocationController implements FxController { return vaultPathIsNull; } + public LocationPresets getLocationPresets() { + return locationPresets; + } + public BooleanProperty usePresetPathProperty() { + return usePresetPath; + } + + public boolean getUsePresetPath() { + return usePresetPath.get(); + } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/LocationPresets.java b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/LocationPresets.java new file mode 100644 index 000000000..a8a889acf --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/LocationPresets.java @@ -0,0 +1,84 @@ +package org.cryptomator.ui.addvaultwizard; + +import javafx.beans.binding.BooleanBinding; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.SimpleObjectProperty; + +import javax.inject.Inject; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +@AddVaultWizardScoped +public class LocationPresets { + + private static final String USER_HOME = System.getProperty("user.home"); + private static final String[] DROPBOX_LOCATIONS = {"~/Dropbox"}; + private static final String[] GDRIVE_LOCATIONS = {"~/Google Drive"}; + + private final ReadOnlyObjectProperty dropboxLocation; + private final ReadOnlyObjectProperty gdriveLocation; + private final BooleanBinding foundDropbox; + private final BooleanBinding foundGdrive; + + @Inject + public LocationPresets() { + this.dropboxLocation = new SimpleObjectProperty<>(existingWritablePath(DROPBOX_LOCATIONS)); + this.gdriveLocation = new SimpleObjectProperty<>(existingWritablePath(GDRIVE_LOCATIONS)); + this.foundDropbox = dropboxLocation.isNotNull(); + this.foundGdrive = gdriveLocation.isNotNull(); + } + + private static Path existingWritablePath(String... candidates) { + for (String candidate : candidates) { + Path path = Paths.get(resolveHomePath(candidate)); + if (Files.isDirectory(path)) { + return path; + } + } + return null; + } + + private static String resolveHomePath(String path) { + if (path.startsWith("~/")) { + return USER_HOME + path.substring(1); + } else { + return path; + } + } + + /* Observables */ + + public ReadOnlyObjectProperty dropboxLocationProperty() { + return dropboxLocation; + } + + public Path getDropboxLocation() { + return dropboxLocation.get(); + } + + public BooleanBinding foundDropboxProperty() { + return foundDropbox; + } + + public boolean isFoundDropbox() { + return foundDropbox.get(); + } + + public ReadOnlyObjectProperty gdriveLocationProperty() { + return gdriveLocation; + } + + public Path getGdriveLocation() { + return gdriveLocation.get(); + } + + public BooleanBinding froundGdriveProperty() { + return foundGdrive; + } + + public boolean isFoundGdrive() { + return foundGdrive.get(); + } + +} diff --git a/main/ui/src/main/resources/css/dark_theme.css b/main/ui/src/main/resources/css/dark_theme.css index 1de8ad7a1..93327c09b 100644 --- a/main/ui/src/main/resources/css/dark_theme.css +++ b/main/ui/src/main/resources/css/dark_theme.css @@ -387,6 +387,39 @@ -fx-background-color: TEXT_FILL; } +/******************************************************************************* + * * + * RadioButton * + * * + ******************************************************************************/ + +.radio-button { + -fx-text-fill: TEXT_FILL; + -fx-label-padding: 0 0 0 6px; +} +.radio-button > .radio { + -fx-border-color: CONTROL_BORDER_NORMAL; + -fx-border-radius: 1em; /* large value to make sure this remains circular */ + -fx-background-color: CONTROL_BG_NORMAL; + -fx-background-radius: 1em; + -fx-padding: 4px; /* padding from outside edge to the inner black dot */ +} +.radio-button:focused > .radio { + -fx-border-color: CONTROL_BORDER_FOCUSED; +} +.text-input:disabled > .radio { + -fx-border-color: CONTROL_BORDER_DISABLED; + -fx-background-color: CONTROL_BG_DISABLED; +} +.radio-button > .radio > .dot { + -fx-background-color: transparent; + -fx-background-radius: 1.0em; /* large value to make sure this remains circular */ + -fx-padding: 3px; /* radius of the inner black dot when selected */ +} +.radio-button:selected > .radio > .dot { + -fx-background-color: TEXT_FILL; +} + /******************************************************************************* * * * Dropdown * diff --git a/main/ui/src/main/resources/css/light_theme.css b/main/ui/src/main/resources/css/light_theme.css index 8211bc0d8..dfd03e1b0 100644 --- a/main/ui/src/main/resources/css/light_theme.css +++ b/main/ui/src/main/resources/css/light_theme.css @@ -387,6 +387,39 @@ -fx-background-color: TEXT_FILL; } +/******************************************************************************* + * * + * RadioButton * + * * + ******************************************************************************/ + +.radio-button { + -fx-text-fill: TEXT_FILL; + -fx-label-padding: 0 0 0 6px; +} +.radio-button > .radio { + -fx-border-color: CONTROL_BORDER_NORMAL; + -fx-border-radius: 1em; /* large value to make sure this remains circular */ + -fx-background-color: CONTROL_BG_NORMAL; + -fx-background-radius: 1em; + -fx-padding: 4px; /* padding from outside edge to the inner black dot */ +} +.radio-button:focused > .radio { + -fx-border-color: CONTROL_BORDER_FOCUSED; +} +.text-input:disabled > .radio { + -fx-border-color: CONTROL_BORDER_DISABLED; + -fx-background-color: CONTROL_BG_DISABLED; +} +.radio-button > .radio > .dot { + -fx-background-color: transparent; + -fx-background-radius: 1.0em; /* large value to make sure this remains circular */ + -fx-padding: 3px; /* radius of the inner black dot when selected */ +} +.radio-button:selected > .radio > .dot { + -fx-background-color: TEXT_FILL; +} + /******************************************************************************* * * * Dropdown * diff --git a/main/ui/src/main/resources/fxml/addvault_new_location.fxml b/main/ui/src/main/resources/fxml/addvault_new_location.fxml index 053a3312f..69ba3689b 100644 --- a/main/ui/src/main/resources/fxml/addvault_new_location.fxml +++ b/main/ui/src/main/resources/fxml/addvault_new_location.fxml @@ -4,26 +4,48 @@ + + + + alignment="BASELINE_LEFT" + spacing="12"> + + + -