diff --git a/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java b/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java index 3178f0ba4..2a69dbcf7 100644 --- a/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java +++ b/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultLocationController.java @@ -13,31 +13,37 @@ import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Named; +import javafx.application.Platform; +import javafx.beans.binding.Bindings; 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.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.fxml.FXML; +import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.Label; import javafx.scene.control.RadioButton; import javafx.scene.control.Toggle; import javafx.scene.control.ToggleGroup; +import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; import javafx.stage.DirectoryChooser; import javafx.stage.Stage; +import javafx.stage.WindowEvent; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; -import java.util.Comparator; -import java.util.List; import java.util.Optional; import java.util.ResourceBundle; +import java.util.concurrent.ExecutorService; @AddVaultWizardScoped public class CreateNewVaultLocationController implements FxController { @@ -49,19 +55,23 @@ public class CreateNewVaultLocationController implements FxController { private final Stage window; private final Lazy chooseNameScene; private final Lazy chooseExpertSettingsScene; - private final List locationPresetBtns; private final ObjectProperty vaultPath; private final StringProperty vaultName; + private final ExecutorService backgroundExecutor; private final ResourceBundle resourceBundle; private final ObservableValue vaultPathStatus; private final ObservableValue validVaultPath; private final BooleanProperty usePresetPath; + private final BooleanProperty loadingPresetLocations = new SimpleBooleanProperty(false); + private final ObservableList radioButtons; + private final ObservableList sortedRadioButtons; private Path customVaultPath = DEFAULT_CUSTOM_VAULT_PATH; //FXML public ToggleGroup locationPresetsToggler; public VBox radioButtonVBox; + public HBox customLocationRadioBtn; public RadioButton customRadioButton; public Label locationStatusLabel; public FontAwesome5IconView goodLocation; @@ -73,25 +83,20 @@ public class CreateNewVaultLocationController implements FxController { @FxmlScene(FxmlFile.ADDVAULT_NEW_EXPERT_SETTINGS) Lazy chooseExpertSettingsScene, // ObjectProperty vaultPath, // @Named("vaultName") StringProperty vaultName, // - ResourceBundle resourceBundle) { + ExecutorService backgroundExecutor, ResourceBundle resourceBundle) { this.window = window; this.chooseNameScene = chooseNameScene; this.chooseExpertSettingsScene = chooseExpertSettingsScene; this.vaultPath = vaultPath; this.vaultName = vaultName; + this.backgroundExecutor = backgroundExecutor; this.resourceBundle = resourceBundle; this.vaultPathStatus = ObservableUtil.mapWithDefault(vaultPath, this::validatePath, new VaultPathStatus(false, "error.message")); this.validVaultPath = ObservableUtil.mapWithDefault(vaultPathStatus, VaultPathStatus::valid, false); this.vaultPathStatus.addListener(this::updateStatusLabel); this.usePresetPath = new SimpleBooleanProperty(); - this.locationPresetBtns = LocationPresetsProvider.loadAll(LocationPresetsProvider.class) // - .flatMap(LocationPresetsProvider::getLocations) // - .sorted(Comparator.comparing(LocationPreset::name)) // - .map(preset -> { // - var btn = new RadioButton(preset.name()); - btn.setUserData(preset.path()); - return btn; - }).toList(); + this.radioButtons = FXCollections.observableArrayList(); + this.sortedRadioButtons = radioButtons.sorted(this::compareLocationPresets); } private VaultPathStatus validatePath(Path p) throws NullPointerException { @@ -137,12 +142,45 @@ public class CreateNewVaultLocationController implements FxController { @FXML public void initialize() { - radioButtonVBox.getChildren().addAll(1, locationPresetBtns); //first item is the list header - locationPresetsToggler.getToggles().addAll(locationPresetBtns); + var task = backgroundExecutor.submit(this::loadLocationPresets); + window.addEventHandler(WindowEvent.WINDOW_HIDING, _ -> task.cancel(true)); locationPresetsToggler.selectedToggleProperty().addListener(this::togglePredefinedLocation); usePresetPath.bind(locationPresetsToggler.selectedToggleProperty().isNotEqualTo(customRadioButton)); + radioButtons.add(customLocationRadioBtn); + Bindings.bindContent(radioButtonVBox.getChildren(), sortedRadioButtons); //to prevent garbage collection of the binding, we bind explicitly to the sorted list } + private void loadLocationPresets() { + Platform.runLater(() -> loadingPresetLocations.set(true)); + try { + LocationPresetsProvider.loadAll(LocationPresetsProvider.class) // + .flatMap(LocationPresetsProvider::getLocations) //we do not use sorted(), because it evaluates the stream elements, blocking until all elements are gathered + .forEach(this::createRadioButtonFor); + } finally { + Platform.runLater(() -> loadingPresetLocations.set(false)); + } + } + + private void createRadioButtonFor(LocationPreset preset) { + Platform.runLater(() -> { + var btn = new RadioButton(preset.name()); + btn.setUserData(preset.path()); + radioButtons.add(btn); + locationPresetsToggler.getToggles().add(btn); + }); + } + + private int compareLocationPresets(Node left, Node right) { + if (customLocationRadioBtn.getId().equals(left.getId())) { + return 1; + } else if (customLocationRadioBtn.getId().equals(right.getId())) { + return -1; + } else { + return ((RadioButton) left).getText().compareToIgnoreCase(((RadioButton) right).getText()); + } + } + + private void togglePredefinedLocation(@SuppressWarnings("unused") ObservableValue observable, @SuppressWarnings("unused") Toggle oldValue, Toggle newValue) { var storagePath = Optional.ofNullable((Path) newValue.getUserData()).orElse(customVaultPath); vaultPath.set(storagePath.resolve(vaultName.get())); @@ -200,6 +238,14 @@ public class CreateNewVaultLocationController implements FxController { return validVaultPath.getValue(); } + public boolean isLoadingPresetLocations() { + return loadingPresetLocations.getValue(); + } + + public BooleanProperty loadingPresetLocationsProperty() { + return loadingPresetLocations; + } + public BooleanProperty usePresetPathProperty() { return usePresetPath; } diff --git a/src/main/resources/fxml/addvault_new_location.fxml b/src/main/resources/fxml/addvault_new_location.fxml index 3374acaa6..7b3157ffb 100644 --- a/src/main/resources/fxml/addvault_new_location.fxml +++ b/src/main/resources/fxml/addvault_new_location.fxml @@ -1,11 +1,13 @@ + + @@ -29,18 +31,26 @@ - - +