Browse Source

Added vault location presets to dialog (references #929)

Sebastian Stenzel 5 năm trước cách đây
mục cha
commit
c6d90cdb17

+ 70 - 38
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<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();
+	}
 }

+ 84 - 0
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<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();
+	}
+
+}

+ 33 - 0
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                                                                    *

+ 33 - 0
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                                                                    *

+ 27 - 5
main/ui/src/main/resources/fxml/addvault_new_location.fxml

@@ -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"/>

+ 1 - 1
main/ui/src/main/resources/i18n/strings.properties

@@ -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: