Added vault location presets to dialog (references #929)

This commit is contained in:
Sebastian Stenzel
2019-08-13 15:43:16 +02:00
parent 50b2b0dc57
commit c6d90cdb17
6 changed files with 248 additions and 44 deletions
@@ -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<Scene> previousScene;
private final Lazy<Scene> nextScene;
private final LocationPresets locationPresets;
private final ObjectProperty<Path> 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<Scene> previousScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD) Lazy<Scene> nextScene, ObjectProperty<Path> vaultPath, StringProperty vaultName, ResourceBundle resourceBundle) {
CreateNewVaultLocationController(@AddVaultWizard Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_NAME) Lazy<Scene> previousScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD) Lazy<Scene> nextScene, LocationPresets locationPresets, ObjectProperty<Path> 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<? extends Toggle> 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();
}
}
@@ -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<Path> dropboxLocation;
private final ReadOnlyObjectProperty<Path> 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<Path> dropboxLocationProperty() {
return dropboxLocation;
}
public Path getDropboxLocation() {
return dropboxLocation.get();
}
public BooleanBinding foundDropboxProperty() {
return foundDropbox;
}
public boolean isFoundDropbox() {
return foundDropbox.get();
}
public ReadOnlyObjectProperty<Path> gdriveLocationProperty() {
return gdriveLocation;
}
public Path getGdriveLocation() {
return gdriveLocation.get();
}
public BooleanBinding froundGdriveProperty() {
return foundGdrive;
}
public boolean isFoundGdrive() {
return foundGdrive.get();
}
}
@@ -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 *
@@ -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 *
@@ -4,26 +4,48 @@
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.ButtonBar?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.RadioButton?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.ToggleGroup?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Region?>
<?import javafx.scene.layout.VBox?>
<?import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView?>
<VBox xmlns="http://javafx.com/javafx"
xmlns:fx="http://javafx.com/fxml"
fx:controller="org.cryptomator.ui.addvaultwizard.CreateNewVaultLocationController"
prefHeight="400.0" prefWidth="600.0"
alignment="CENTER">
alignment="BASELINE_LEFT"
spacing="12">
<fx:define>
<ToggleGroup fx:id="predefinedLocationToggler"/>
</fx:define>
<padding>
<Insets top="12" right="12" bottom="12" left="12"/>
</padding>
<children>
<Region VBox.vgrow="ALWAYS"/>
<Label text="%addvaultwizard.new.locationInstruction"/>
<HBox spacing="18">
<TextField promptText="%addvaultwizard.new.locationPrompt" text="${controller.vaultPath}" disable="true" HBox.hgrow="ALWAYS"/>
<Button text="%addvaultwizard.new.directoryPickerButton" onAction="#chooseDirectory" HBox.hgrow="NEVER" prefWidth="120"/>
<Label wrapText="true" text="%addvaultwizard.new.locationInstruction"/>
<Region prefHeight="24" VBox.vgrow="NEVER"/>
<RadioButton fx:id="dropboxRadioButton" toggleGroup="${predefinedLocationToggler}" text="TODO dropbox" visible="${controller.locationPresets.foundDropbox}"/>
<RadioButton fx:id="gdriveRadioButton" toggleGroup="${predefinedLocationToggler}" text="TODO google drive" visible="${controller.locationPresets.foundGdrive}"/>
<HBox spacing="12" alignment="BASELINE_LEFT">
<RadioButton fx:id="customRadioButton" toggleGroup="${predefinedLocationToggler}" text="TODO custom location"/>
<Button contentDisplay="LEFT" text="%addvaultwizard.new.directoryPickerButton" onAction="#chooseCustomVaultPath" disable="${controller.usePresetPath}">
<graphic>
<FontAwesomeIconView styleClass="fa-icons" glyphName="FOLDER_OPEN"/>
</graphic>
</Button>
</HBox>
<Region prefHeight="24" VBox.vgrow="NEVER"/>
<TextField promptText="%addvaultwizard.new.locationPrompt" text="${controller.vaultPath}" disable="true" HBox.hgrow="ALWAYS"/>
<Region VBox.vgrow="ALWAYS"/>
<ButtonBar buttonMinWidth="120" buttonOrder="B+X">
<buttons>
<Button text="%generic.button.back" ButtonBar.buttonData="BACK_PREVIOUS" onAction="#back"/>
@@ -21,7 +21,7 @@ addvaultwizard.new.nameInstruction=Please enter a name for the vault:
addvaultwizard.new.namePrompt=TODO vault name
addvaultwizard.new.locationInstruction=Please pick a directory where your vault will be stored:
addvaultwizard.new.locationPrompt=TODO location
addvaultwizard.new.directoryPickerButton=TODO DirPicker
addvaultwizard.new.directoryPickerButton=Choose...
addvaultwizard.new.directoryPickerTitle=Select Directory
addvaultwizard.new.enterPassword=Please enter a Password for your vault:
addvaultwizard.new.reenterPassword=Please retype the password: