diff --git a/.crowdin.yml b/.crowdin.yml new file mode 100644 index 000000000..719c29826 --- /dev/null +++ b/.crowdin.yml @@ -0,0 +1,5 @@ +commit_message: "[ci skip]" +escape_special_characters: 0 +files: + - source: /main/ui/src/main/resources/i18n/strings.properties + translation: /main/ui/src/main/resources/i18n/strings_%two_letters_code%.properties diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 0af73f145..a0d7519cd 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,6 +1,6 @@ # These are supported funding model platforms -github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +github: [overheadhunter] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username diff --git a/README.md b/README.md index 417dfb09a..2f8c79a3d 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Known Vulnerabilities](https://snyk.io/test/github/cryptomator/cryptomator/badge.svg?targetFile=main%2Fpom.xml)](https://snyk.io/test/github/cryptomator/cryptomator?targetFile=main%2Fpom.xml) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/2a0adf3cec6a4143b91035d3924178f1)](https://www.codacy.com/app/cryptomator/cryptomator?utm_source=github.com&utm_medium=referral&utm_content=cryptomator/cryptomator&utm_campaign=Badge_Grade) [![Twitter](https://img.shields.io/badge/twitter-@Cryptomator-blue.svg?style=flat)](http://twitter.com/Cryptomator) -[![POEditor](https://img.shields.io/badge/POEditor-Help%20Translate-blue.svg?style=flat)](https://poeditor.com/join/project/bHwbvJmx0E) +[![Crowdin](https://badges.crowdin.net/cryptomator/localized.svg)](https://translate.cryptomator.org/) [![Latest Release](https://img.shields.io/github/release/cryptomator/cryptomator.svg)](https://github.com/cryptomator/cryptomator/releases/latest) [![Community](https://img.shields.io/badge/help-Community-orange.svg)](https://community.cryptomator.org) diff --git a/main/buildkit/pom.xml b/main/buildkit/pom.xml index c9e62ac5d..9e7251134 100644 --- a/main/buildkit/pom.xml +++ b/main/buildkit/pom.xml @@ -4,7 +4,7 @@ org.cryptomator main - 1.5.0-alpha1 + 1.5.0-alpha2 buildkit pom diff --git a/main/commons/pom.xml b/main/commons/pom.xml index 611bb32df..5d7526970 100644 --- a/main/commons/pom.xml +++ b/main/commons/pom.xml @@ -4,7 +4,7 @@ org.cryptomator main - 1.5.0-alpha1 + 1.5.0-alpha2 commons Cryptomator Commons diff --git a/main/keychain/pom.xml b/main/keychain/pom.xml index e707a5177..0bcdce324 100644 --- a/main/keychain/pom.xml +++ b/main/keychain/pom.xml @@ -4,7 +4,7 @@ org.cryptomator main - 1.5.0-alpha1 + 1.5.0-alpha2 keychain System Keychain Access diff --git a/main/launcher/pom.xml b/main/launcher/pom.xml index 5a781d8fc..247e4eb47 100644 --- a/main/launcher/pom.xml +++ b/main/launcher/pom.xml @@ -4,7 +4,7 @@ org.cryptomator main - 1.5.0-alpha1 + 1.5.0-alpha2 launcher Cryptomator Launcher diff --git a/main/pom.xml b/main/pom.xml index fabe7239c..87522602c 100644 --- a/main/pom.xml +++ b/main/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.cryptomator main - 1.5.0-alpha1 + 1.5.0-alpha2 pom Cryptomator @@ -24,14 +24,13 @@ UTF-8 - 1.2.2 - 1.9.0-beta1 + 1.9.0-beta4 2.2.1 1.2.0 1.1.11 1.0.10 - 12 + 13 2.6 3.8.1 @@ -82,11 +81,6 @@ - - org.cryptomator - cryptolib - ${cryptomator.cryptolib.version} - org.cryptomator cryptofs diff --git a/main/ui/pom.xml b/main/ui/pom.xml index ff3abcc2f..c171a9794 100644 --- a/main/ui/pom.xml +++ b/main/ui/pom.xml @@ -4,7 +4,7 @@ org.cryptomator main - 1.5.0-alpha1 + 1.5.0-alpha2 ui Cryptomator GUI @@ -22,10 +22,6 @@ org.cryptomator jni - - org.cryptomator - cryptolib - diff --git a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultFailureExisitingController.java b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultFailureExisitingController.java new file mode 100644 index 000000000..201f53345 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultFailureExisitingController.java @@ -0,0 +1,51 @@ +package org.cryptomator.ui.addvaultwizard; + +import dagger.Lazy; +import javafx.beans.binding.Bindings; +import javafx.beans.binding.StringBinding; +import javafx.beans.property.ObjectProperty; +import javafx.fxml.FXML; +import javafx.scene.Scene; +import javafx.stage.Stage; +import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.common.FxmlFile; +import org.cryptomator.ui.common.FxmlScene; + +import javax.inject.Inject; +import java.nio.file.Path; + +@AddVaultWizardScoped +public class AddVaultFailureExisitingController implements FxController { + + private final Stage window; + private final Lazy previousScene; + private final StringBinding vaultName; + + @Inject + AddVaultFailureExisitingController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_EXISTING) Lazy previousScene, ObjectProperty pathOfFailedVault){ + this.window = window; + this.previousScene = previousScene; + this.vaultName = Bindings.createStringBinding(() -> pathOfFailedVault.get().getFileName().toString(),pathOfFailedVault); + } + + @FXML + public void close(){ + window.close(); + } + + @FXML + public void back(){ + window.setScene(previousScene.get()); + } + + // Getter & Setter + + public StringBinding vaultNameProperty(){ + return vaultName; + } + + public String getVaultName(){ + return vaultName.get(); + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultModule.java b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultModule.java index 8ec04383f..4eaa1c207 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultModule.java +++ b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/AddVaultModule.java @@ -1,24 +1,19 @@ package org.cryptomator.ui.addvaultwizard; import dagger.Binds; -import dagger.Lazy; import dagger.Module; import dagger.Provides; import dagger.multibindings.IntoMap; -import dagger.multibindings.IntoSet; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.scene.Scene; import javafx.scene.image.Image; -import javafx.scene.input.KeyCode; -import javafx.scene.input.KeyCodeCombination; -import javafx.scene.input.KeyCombination; import javafx.stage.Modality; import javafx.stage.Stage; -import org.apache.commons.lang3.SystemUtils; import org.cryptomator.common.vaults.Vault; +import org.cryptomator.ui.common.DefaultSceneFactory; import org.cryptomator.ui.common.FXMLLoaderFactory; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxControllerKey; @@ -32,7 +27,6 @@ import java.nio.file.Path; import java.util.Map; import java.util.Optional; import java.util.ResourceBundle; -import java.util.Set; @Module public abstract class AddVaultModule { @@ -40,22 +34,19 @@ public abstract class AddVaultModule { @Provides @AddVaultWizardWindow @AddVaultWizardScoped - static FXMLLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, ResourceBundle resourceBundle) { - return new FXMLLoaderFactory(factories, resourceBundle); + static FXMLLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) { + return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle); } @Provides @AddVaultWizardWindow @AddVaultWizardScoped - static Stage provideStage(@MainWindow Stage owner, ResourceBundle resourceBundle, @Named("windowIcon") Optional windowIcon, @AddVaultWizardWindow Lazy> accelerators) { + static Stage provideStage(@MainWindow Stage owner, ResourceBundle resourceBundle, @Named("windowIcon") Optional windowIcon) { Stage stage = new Stage(); stage.setTitle(resourceBundle.getString("addvaultwizard.title")); stage.setResizable(false); stage.initModality(Modality.WINDOW_MODAL); stage.initOwner(owner); - stage.sceneProperty().addListener(observable -> { - stage.getScene().getAccelerators().putAll(accelerators.get()); - }); windowIcon.ifPresent(stage.getIcons()::add); return stage; } @@ -67,6 +58,7 @@ public abstract class AddVaultModule { } @Provides + @Named("vaultName") @AddVaultWizardScoped static StringProperty provideVaultName() { return new SimpleStringProperty(""); @@ -79,24 +71,11 @@ public abstract class AddVaultModule { return new SimpleObjectProperty<>(); } - // ------------------ - @Provides - @AddVaultWizardWindow + @Named("recoveryKey") @AddVaultWizardScoped - static Map provideDefaultAccellerators(@AddVaultWizardWindow Set> accelerators) { - return Map.ofEntries(accelerators.toArray(Map.Entry[]::new)); - } - - @Provides - @IntoSet - @AddVaultWizardWindow - static Map.Entry provideCloseWindowShortcut(@AddVaultWizardWindow Stage window) { - if (SystemUtils.IS_OS_WINDOWS) { - return Map.entry(new KeyCodeCombination(KeyCode.F4, KeyCombination.ALT_DOWN), window::close); - } else { - return Map.entry(new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN), window::close); - } + static StringProperty provideRecoveryKey() { + return new SimpleStringProperty(); } // ------------------ @@ -104,48 +83,57 @@ public abstract class AddVaultModule { @Provides @FxmlScene(FxmlFile.ADDVAULT_WELCOME) @AddVaultWizardScoped - static Scene provideWelcomeScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders, @AddVaultWizardWindow Stage window) { - Scene scene = fxmlLoaders.createScene("/fxml/addvault_welcome.fxml"); - - KeyCombination cmdW = new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN); - scene.getAccelerators().put(cmdW, window::close); - - return scene; + static Scene provideWelcomeScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene(FxmlFile.ADDVAULT_WELCOME.getRessourcePathString()); } @Provides @FxmlScene(FxmlFile.ADDVAULT_EXISTING) @AddVaultWizardScoped - static Scene provideChooseExistingVaultScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders, @AddVaultWizardWindow Stage window) { - return fxmlLoaders.createScene("/fxml/addvault_existing.fxml"); + static Scene provideChooseExistingVaultScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene(FxmlFile.ADDVAULT_EXISTING.getRessourcePathString()); + } + + @Provides + @FxmlScene(FxmlFile.ADDVAULT_EXISTING_ERROR) + @AddVaultWizardScoped + static Scene provideChooseExistingVaultErrorScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene(FxmlFile.ADDVAULT_EXISTING_ERROR.getRessourcePathString()); } @Provides @FxmlScene(FxmlFile.ADDVAULT_NEW_NAME) @AddVaultWizardScoped - static Scene provideCreateNewVaultNameScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders, @AddVaultWizardWindow Stage window) { - return fxmlLoaders.createScene("/fxml/addvault_new_name.fxml"); + static Scene provideCreateNewVaultNameScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_NAME.getRessourcePathString()); } @Provides @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) @AddVaultWizardScoped - static Scene provideCreateNewVaultLocationScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders, @AddVaultWizardWindow Stage window) { - return fxmlLoaders.createScene("/fxml/addvault_new_location.fxml"); + static Scene provideCreateNewVaultLocationScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_LOCATION.getRessourcePathString()); } @Provides @FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD) @AddVaultWizardScoped - static Scene provideCreateNewVaultPasswordScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders, @AddVaultWizardWindow Stage window) { - return fxmlLoaders.createScene("/fxml/addvault_new_password.fxml"); + static Scene provideCreateNewVaultPasswordScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_PASSWORD.getRessourcePathString()); + } + + @Provides + @FxmlScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY) + @AddVaultWizardScoped + static Scene provideCreateNewVaultRecoveryKeyScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY.getRessourcePathString()); } @Provides @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) @AddVaultWizardScoped - static Scene provideCreateNewVaultSuccessScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders, @AddVaultWizardWindow Stage window) { - return fxmlLoaders.createScene("/fxml/addvault_success.fxml"); + static Scene provideCreateNewVaultSuccessScene(@AddVaultWizardWindow FXMLLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene(FxmlFile.ADDVAULT_SUCCESS.getRessourcePathString()); } // ------------------ @@ -160,6 +148,11 @@ public abstract class AddVaultModule { @FxControllerKey(ChooseExistingVaultController.class) abstract FxController bindChooseExistingVaultController(ChooseExistingVaultController controller); + @Binds + @IntoMap + @FxControllerKey(AddVaultFailureExisitingController.class) + abstract FxController bindAddVaultFailureExistingController(AddVaultFailureExisitingController controller); + @Binds @IntoMap @FxControllerKey(CreateNewVaultNameController.class) @@ -179,4 +172,9 @@ public abstract class AddVaultModule { @IntoMap @FxControllerKey(AddVaultSuccessController.class) abstract FxController bindAddVaultSuccessController(AddVaultSuccessController controller); + + @Binds + @IntoMap + @FxControllerKey(CreateNewVaultRecoveryKeyController.class) + abstract FxController bindCreateNewVaultRecoveryKeyController(CreateNewVaultRecoveryKeyController controller); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java index 8856dc4cf..8bd11139a 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/ChooseExistingVaultController.java @@ -28,16 +28,18 @@ public class ChooseExistingVaultController implements FxController { private final Stage window; private final Lazy welcomeScene; private final Lazy successScene; + private final Lazy errorScene; private final ObjectProperty vaultPath; private final ObjectProperty vault; private final VaultListManager vaultListManager; private final ResourceBundle resourceBundle; @Inject - ChooseExistingVaultController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy successScene, ObjectProperty vaultPath, @AddVaultWizardWindow ObjectProperty vault, VaultListManager vaultListManager, ResourceBundle resourceBundle) { + ChooseExistingVaultController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy successScene, @FxmlScene(FxmlFile.ADDVAULT_EXISTING_ERROR) Lazy errorScene, ObjectProperty vaultPath, @AddVaultWizardWindow ObjectProperty vault, VaultListManager vaultListManager, ResourceBundle resourceBundle) { this.window = window; this.welcomeScene = welcomeScene; this.successScene = successScene; + this.errorScene = errorScene; this.vaultPath = vaultPath; this.vault = vault; this.vaultListManager = vaultListManager; @@ -51,20 +53,19 @@ public class ChooseExistingVaultController implements FxController { @FXML public void chooseFileAndNext() { - //TODO: error handling & cannot unlock added vault FileChooser fileChooser = new FileChooser(); fileChooser.setTitle(resourceBundle.getString("addvaultwizard.existing.filePickerTitle")); fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Cryptomator Masterkey", "*.cryptomator")); - File file = fileChooser.showOpenDialog(window); - if (file != null) { - vaultPath.setValue(file.toPath().toAbsolutePath().getParent()); + File masterkeyFile = fileChooser.showOpenDialog(window); + if (masterkeyFile != null) { + vaultPath.setValue(masterkeyFile.toPath().toAbsolutePath().getParent()); try { Vault newVault = vaultListManager.add(vaultPath.get()); vault.set(newVault); window.setScene(successScene.get()); } catch (NoSuchFileException e) { - LOG.error("Nope", e); - // TODO + LOG.error("Failed to open existing vault.", e); + window.setScene(errorScene.get()); } } } 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 f766d2fce..e15d195bf 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 @@ -23,12 +23,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; +import javax.inject.Named; 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; @@ -58,7 +57,7 @@ public class CreateNewVaultLocationController implements FxController { public RadioButton customRadioButton; @Inject - CreateNewVaultLocationController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_NAME) Lazy chooseNameScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD) Lazy choosePasswordScene, LocationPresets locationPresets, ObjectProperty vaultPath, StringProperty vaultName, ResourceBundle resourceBundle) { + CreateNewVaultLocationController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_NAME) Lazy chooseNameScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_PASSWORD) Lazy choosePasswordScene, LocationPresets locationPresets, ObjectProperty vaultPath, @Named("vaultName") StringProperty vaultName, ResourceBundle resourceBundle) { this.window = window; this.chooseNameScene = chooseNameScene; this.choosePasswordScene = choosePasswordScene; @@ -115,14 +114,12 @@ public class CreateNewVaultLocationController implements FxController { Files.delete(createdDir); // assert: dir exists and is empty window.setScene(choosePasswordScene.get()); } catch (FileAlreadyExistsException e) { - LOG.warn("Can not use already existing vault path: {}", vaultPath.get()); + LOG.warn("Can not use already existing vault path {}", vaultPath.get()); warningText.set(resourceBundle.getString("addvaultwizard.new.fileAlreadyExists")); - } catch (NoSuchFileException | DirectoryNotEmptyException e) { - LOG.error("Failed to delete recently created directory.", e); - // TODO show generic error text for unexpected exception } catch (IOException e) { LOG.warn("Can not create vault at path: {}", vaultPath.get()); - // TODO show generic error text for unexpected exception + LOG.warn("Thrown Exception:", e); + warningText.set(resourceBundle.getString("addvaultwizard.new.ioException")); } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultNameController.java b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultNameController.java index 8cc8fa9a2..57b8bed04 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultNameController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultNameController.java @@ -16,6 +16,7 @@ import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; import javax.inject.Inject; +import javax.inject.Named; import java.nio.file.Path; import java.util.ResourceBundle; import java.util.regex.Pattern; @@ -36,7 +37,7 @@ public class CreateNewVaultNameController implements FxController { private final StringBinding warningText; @Inject - CreateNewVaultNameController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy chooseLocationScene, ObjectProperty vaultPath, StringProperty vaultName, ResourceBundle resourceBundle) { + CreateNewVaultNameController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_WELCOME) Lazy welcomeScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy chooseLocationScene, ObjectProperty vaultPath, @Named("vaultName") StringProperty vaultName, ResourceBundle resourceBundle) { this.window = window; this.welcomeScene = welcomeScene; this.chooseLocationScene = chooseLocationScene; diff --git a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java index 26091ec73..ab2fe27a0 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultPasswordController.java @@ -12,7 +12,6 @@ import javafx.beans.property.SimpleIntegerProperty; import javafx.beans.property.StringProperty; import javafx.fxml.FXML; import javafx.scene.Scene; -import javafx.scene.control.CheckBox; import javafx.scene.control.ContentDisplay; import javafx.scene.control.Label; import javafx.scene.layout.HBox; @@ -28,11 +27,13 @@ import org.cryptomator.ui.common.Tasks; import org.cryptomator.ui.controls.FontAwesome5IconView; import org.cryptomator.ui.controls.NiceSecurePasswordField; import org.cryptomator.ui.common.PasswordStrengthUtil; +import org.cryptomator.ui.recoverykey.RecoveryKeyFactory; import org.fxmisc.easybind.EasyBind; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; +import javax.inject.Named; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.channels.WritableByteChannel; @@ -56,11 +57,13 @@ public class CreateNewVaultPasswordController implements FxController { private final Stage window; private final Lazy chooseLocationScene; - private final Lazy successScene; + private final Lazy recoveryKeyScene; private final ExecutorService executor; - private final StringProperty vaultName; - private final ObjectProperty vaultPath; - private final ObjectProperty vault; + private final RecoveryKeyFactory recoveryKeyFactory; + private final StringProperty vaultNameProperty; + private final ObjectProperty vaultPathProperty; + private final ObjectProperty vaultProperty; + private final StringProperty recoveryKeyProperty; private final VaultListManager vaultListManager; private final ResourceBundle resourceBundle; private final PasswordStrengthUtil strengthRater; @@ -77,17 +80,18 @@ public class CreateNewVaultPasswordController implements FxController { public FontAwesome5IconView checkmark; public FontAwesome5IconView cross; public Label passwordMatchLabel; - public CheckBox finalConfirmationCheckbox; @Inject - CreateNewVaultPasswordController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy chooseLocationScene, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy successScene, ExecutorService executor, StringProperty vaultName, ObjectProperty vaultPath, @AddVaultWizardWindow ObjectProperty vault, VaultListManager vaultListManager, ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater, ReadmeGenerator readmeGenerator) { + CreateNewVaultPasswordController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_NEW_LOCATION) Lazy chooseLocationScene, @FxmlScene(FxmlFile.ADDVAULT_NEW_RECOVERYKEY) Lazy recoveryKeyScene, ExecutorService executor, RecoveryKeyFactory recoveryKeyFactory, @Named("vaultName") StringProperty vaultName, ObjectProperty vaultPath, @AddVaultWizardWindow ObjectProperty vault, @Named("recoveryKey") StringProperty recoveryKey, VaultListManager vaultListManager, ResourceBundle resourceBundle, PasswordStrengthUtil strengthRater, ReadmeGenerator readmeGenerator) { this.window = window; this.chooseLocationScene = chooseLocationScene; - this.successScene = successScene; + this.recoveryKeyScene = recoveryKeyScene; this.executor = executor; - this.vaultName = vaultName; - this.vaultPath = vaultPath; - this.vault = vault; + this.recoveryKeyFactory = recoveryKeyFactory; + this.vaultNameProperty = vaultName; + this.vaultPathProperty = vaultPath; + this.vaultProperty = vault; + this.recoveryKeyProperty = recoveryKey; this.vaultListManager = vaultListManager; this.resourceBundle = resourceBundle; this.strengthRater = strengthRater; @@ -105,7 +109,7 @@ public class CreateNewVaultPasswordController implements FxController { //binding indicating if the passwords not match BooleanBinding passwordsMatch = Bindings.createBooleanBinding(() -> CharSequence.compare(passwordField.getCharacters(), reenterField.getCharacters()) == 0, passwordField.textProperty(), reenterField.textProperty()); BooleanBinding reenterFieldNotEmpty = reenterField.textProperty().isNotEmpty(); - readyToCreateVault.bind(reenterFieldNotEmpty.and(passwordsMatch).and(finalConfirmationCheckbox.selectedProperty()).and(processing.not())); + readyToCreateVault.bind(reenterFieldNotEmpty.and(passwordsMatch).and(processing.not())); //make match indicator invisible when passwords do not match or one is empty passwordMatchBox.visibleProperty().bind(reenterFieldNotEmpty); checkmark.visibleProperty().bind(passwordsMatch.and(reenterFieldNotEmpty)); @@ -125,7 +129,7 @@ public class CreateNewVaultPasswordController implements FxController { @FXML public void next() { - Path pathToVault = vaultPath.get(); + Path pathToVault = vaultPathProperty.get(); try { Files.createDirectory(pathToVault); @@ -140,8 +144,9 @@ public class CreateNewVaultPasswordController implements FxController { processing.set(true); Tasks.create(() -> { initializeVault(pathToVault, passwordField.getCharacters()); - }).onSuccess(() -> { - initializationSucceeded(pathToVault); + return recoveryKeyFactory.createRecoveryKey(pathToVault, passwordField.getCharacters()); + }).onSuccess(recoveryKey -> { + initializationSucceeded(pathToVault, recoveryKey); }).onError(IOException.class, e -> { // TODO show generic error screen LOG.error("", e); @@ -171,11 +176,12 @@ public class CreateNewVaultPasswordController implements FxController { LOG.info("Created vault at {}", path); } - private void initializationSucceeded(Path pathToVault) { + private void initializationSucceeded(Path pathToVault, String recoveryKey) { try { Vault newVault = vaultListManager.add(pathToVault); - vault.set(newVault); - window.setScene(successScene.get()); + vaultProperty.set(newVault); + recoveryKeyProperty.set(recoveryKey); + window.setScene(recoveryKeyScene.get()); } catch (NoSuchFileException e) { throw new UncheckedIOException(e); } @@ -184,11 +190,11 @@ public class CreateNewVaultPasswordController implements FxController { /* Getter/Setter */ public String getVaultName() { - return vaultName.get(); + return vaultNameProperty.get(); } public StringProperty vaultNameProperty() { - return vaultName; + return vaultNameProperty; } public IntegerProperty passwordStrengthProperty() { diff --git a/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultRecoveryKeyController.java b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultRecoveryKeyController.java new file mode 100644 index 000000000..4c38f1250 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/addvaultwizard/CreateNewVaultRecoveryKeyController.java @@ -0,0 +1,42 @@ +package org.cryptomator.ui.addvaultwizard; + +import dagger.Lazy; +import javafx.beans.property.StringProperty; +import javafx.fxml.FXML; +import javafx.scene.Scene; +import javafx.stage.Stage; +import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.common.FxmlFile; +import org.cryptomator.ui.common.FxmlScene; + +import javax.inject.Inject; +import javax.inject.Named; + +public class CreateNewVaultRecoveryKeyController implements FxController { + + private final Stage window; + private final Lazy successScene; + private final StringProperty recoveryKeyProperty; + + @Inject + CreateNewVaultRecoveryKeyController(@AddVaultWizardWindow Stage window, @FxmlScene(FxmlFile.ADDVAULT_SUCCESS) Lazy successScene, @Named("recoveryKey")StringProperty recoveryKey) { + this.window = window; + this.successScene = successScene; + this.recoveryKeyProperty = recoveryKey; + } + + @FXML + public void next() { + window.setScene(successScene.get()); + } + + /* Getter/Setter */ + + public String getRecoveryKey() { + return recoveryKeyProperty.get(); + } + + public StringProperty recoveryKeyProperty() { + return recoveryKeyProperty; + } +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordModule.java b/main/ui/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordModule.java index f47e18fcb..7a2262aee 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordModule.java +++ b/main/ui/src/main/java/org/cryptomator/ui/changepassword/ChangePasswordModule.java @@ -8,6 +8,7 @@ import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.stage.Modality; import javafx.stage.Stage; +import org.cryptomator.ui.common.DefaultSceneFactory; import org.cryptomator.ui.common.FXMLLoaderFactory; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxControllerKey; @@ -26,8 +27,8 @@ abstract class ChangePasswordModule { @Provides @ChangePasswordWindow @ChangePasswordScoped - static FXMLLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, ResourceBundle resourceBundle) { - return new FXMLLoaderFactory(factories, resourceBundle); + static FXMLLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) { + return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle); } @Provides diff --git a/main/ui/src/main/java/org/cryptomator/ui/common/DefaultSceneFactory.java b/main/ui/src/main/java/org/cryptomator/ui/common/DefaultSceneFactory.java new file mode 100644 index 000000000..14efe8f27 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/common/DefaultSceneFactory.java @@ -0,0 +1,49 @@ +package org.cryptomator.ui.common; + +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.input.KeyCombination; +import javafx.stage.Stage; +import javafx.stage.Window; +import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.ui.fxapp.FxApplicationScoped; + +import javax.inject.Inject; +import java.util.function.Function; + +@FxApplicationScoped +public class DefaultSceneFactory implements Function { + + protected static final KeyCodeCombination ALT_F4 = new KeyCodeCombination(KeyCode.F4, KeyCombination.ALT_DOWN); + protected static final KeyCodeCombination SHORTCUT_W = new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN); + + @Inject + public DefaultSceneFactory() {} + + @Override + public Scene apply(Parent root) { + Scene scene = new Scene(root); + configureScene(scene); + return scene; + } + + protected void configureScene(Scene scene) { + scene.windowProperty().addListener(observable -> { + Window window = scene.getWindow(); + if (window instanceof Stage) { + setupDefaultAccelerators(scene, (Stage) window); + } + }); + } + + protected void setupDefaultAccelerators(Scene scene, Stage stage) { + if (SystemUtils.IS_OS_WINDOWS) { + scene.getAccelerators().put(ALT_F4, stage::close); + } else { + scene.getAccelerators().put(SHORTCUT_W, stage::close); + } + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/common/FXMLLoaderFactory.java b/main/ui/src/main/java/org/cryptomator/ui/common/FXMLLoaderFactory.java index dcd0072b2..1edcc633b 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/common/FXMLLoaderFactory.java +++ b/main/ui/src/main/java/org/cryptomator/ui/common/FXMLLoaderFactory.java @@ -1,24 +1,28 @@ package org.cryptomator.ui.common; +import com.google.common.base.Splitter; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; -import javafx.stage.Stage; import javax.inject.Provider; import java.io.IOException; import java.io.InputStream; import java.io.UncheckedIOException; +import java.util.List; import java.util.Map; import java.util.ResourceBundle; +import java.util.function.Function; public class FXMLLoaderFactory { - private final Map, Provider> factories; + private final Map, Provider> controllerFactories; + private final Function sceneFactory; private final ResourceBundle resourceBundle; - public FXMLLoaderFactory(Map, Provider> factories, ResourceBundle resourceBundle) { - this.factories = factories; + public FXMLLoaderFactory(Map, Provider> controllerFactories, Function sceneFactory, ResourceBundle resourceBundle) { + this.controllerFactories = controllerFactories; + this.sceneFactory = sceneFactory; this.resourceBundle = resourceBundle; } @@ -34,6 +38,7 @@ public class FXMLLoaderFactory { /** * Loads the FXML given fxml resource in a new FXMLLoader instance. + * * @param fxmlResourceName Name of the resource (as in {@link Class#getResource(String)}). * @return The FXMLLoader used to load the file * @throws IOException if an error occurs while loading the FXML file @@ -48,6 +53,7 @@ public class FXMLLoaderFactory { /** * {@link #load(String) Loads} the FXML file and creates a new Scene containing the loaded ui. + * * @param fxmlResourceName Name of the resource (as in {@link Class#getResource(String)}). * @throws UncheckedIOException wrapping any IOException thrown by {@link #load(String)). */ @@ -59,14 +65,16 @@ public class FXMLLoaderFactory { throw new UncheckedIOException("Failed to load " + fxmlResourceName, e); } Parent root = loader.getRoot(); - return new Scene(root); + List addtionalStyleSheets = Splitter.on(',').omitEmptyStrings().splitToList(resourceBundle.getString("additionalStyleSheets")); + addtionalStyleSheets.forEach(styleSheet -> root.getStylesheets().add("/css/" + styleSheet)); + return sceneFactory.apply(root); } private FxController constructController(Class aClass) { - if (!factories.containsKey(aClass)) { + if (!controllerFactories.containsKey(aClass)) { throw new IllegalArgumentException("ViewController not registered: " + aClass); } else { - return factories.get(aClass).get(); + return controllerFactories.get(aClass).get(); } } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/common/FxmlFile.java b/main/ui/src/main/java/org/cryptomator/ui/common/FxmlFile.java index fc449a1ca..553e70bef 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/common/FxmlFile.java +++ b/main/ui/src/main/java/org/cryptomator/ui/common/FxmlFile.java @@ -3,9 +3,11 @@ package org.cryptomator.ui.common; public enum FxmlFile { ADDVAULT_WELCOME("/fxml/addvault_welcome.fxml"), // ADDVAULT_EXISTING("/fxml/addvault_existing.fxml"), // + ADDVAULT_EXISTING_ERROR("/fxml/addvault_existing_error.fxml"), ADDVAULT_NEW_NAME("/fxml/addvault_new_name.fxml"), // ADDVAULT_NEW_LOCATION("/fxml/addvault_new_location.fxml"), // ADDVAULT_NEW_PASSWORD("/fxml/addvault_new_password.fxml"), // + ADDVAULT_NEW_RECOVERYKEY("/fxml/addvault_new_recoverykey.fxml"), // ADDVAULT_SUCCESS("/fxml/addvault_success.fxml"), // CHANGEPASSWORD("/fxml/changepassword.fxml"), // FORGET_PASSWORD("/fxml/forget_password.fxml"), // @@ -15,15 +17,21 @@ public enum FxmlFile { MIGRATION_SUCCESS("/fxml/migration_success.fxml"), // PREFERENCES("/fxml/preferences.fxml"), // QUIT("/fxml/quit.fxml"), // + RECOVERYKEY_CREATE("/fxml/recoverykey_create.fxml"), // + RECOVERYKEY_DISPLAY("/fxml/recoverykey_display.fxml"), // REMOVE_VAULT("/fxml/remove_vault.fxml"), // - UNLOCK("/fxml/unlock2.fxml"), // TODO rename + UNLOCK("/fxml/unlock.fxml"), UNLOCK_SUCCESS("/fxml/unlock_success.fxml"), // VAULT_OPTIONS("/fxml/vault_options.fxml"), // WRONGFILEALERT("/fxml/wrongfilealert.fxml"); - private final String filename; + private final String ressourcePathString; - FxmlFile(String filename) { - this.filename = filename; + FxmlFile(String ressourcePathString) { + this.ressourcePathString = ressourcePathString; + } + + public String getRessourcePathString(){ + return ressourcePathString; } } diff --git a/main/ui/src/main/java/org/cryptomator/ui/common/PasswordStrengthUtil.java b/main/ui/src/main/java/org/cryptomator/ui/common/PasswordStrengthUtil.java index a9bc7e760..76385c372 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/common/PasswordStrengthUtil.java +++ b/main/ui/src/main/java/org/cryptomator/ui/common/PasswordStrengthUtil.java @@ -31,8 +31,7 @@ public class PasswordStrengthUtil { public PasswordStrengthUtil(ResourceBundle resourceBundle) { this.resourceBundle = resourceBundle; this.zxcvbn = new Zxcvbn(); - this.sanitizedInputs = new ArrayList<>(); - this.sanitizedInputs.add("cryptomator"); + this.sanitizedInputs = List.of("cryptomator"); } public int computeRate(String password) { diff --git a/main/ui/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java b/main/ui/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java index 070f4519d..f5b62ae2b 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java @@ -9,6 +9,7 @@ public enum FontAwesome5Icon { CHECK("\uF00C"), // COG("\uF013"), // COGS("\uF085"), // + EXCLAMATION("\uF12A"), EXCLAMATION_TRIANGLE("\uF071"), // EYE("\uF06E"), // EYE_SLASH("\uF070"), // diff --git a/main/ui/src/main/java/org/cryptomator/ui/controls/NiceSecurePasswordField.java b/main/ui/src/main/java/org/cryptomator/ui/controls/NiceSecurePasswordField.java index b727ccc78..3a85fbad6 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/controls/NiceSecurePasswordField.java +++ b/main/ui/src/main/java/org/cryptomator/ui/controls/NiceSecurePasswordField.java @@ -67,6 +67,10 @@ public class NiceSecurePasswordField extends StackPane { public void requestFocus() { passwordField.requestFocus(); } + + public String getText() { + return passwordField.getText(); + } public StringProperty textProperty() { return passwordField.textProperty(); @@ -76,6 +80,10 @@ public class NiceSecurePasswordField extends StackPane { return passwordField.getCharacters(); } + public void setPassword(CharSequence password) { + passwordField.setPassword(password); + } + public void setPassword(char[] password) { passwordField.setPassword(password); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/forgetPassword/ForgetPasswordModule.java b/main/ui/src/main/java/org/cryptomator/ui/forgetPassword/ForgetPasswordModule.java index f0d055db7..b06f8dec0 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/forgetPassword/ForgetPasswordModule.java +++ b/main/ui/src/main/java/org/cryptomator/ui/forgetPassword/ForgetPasswordModule.java @@ -11,6 +11,7 @@ import javafx.scene.Scene; import javafx.scene.image.Image; import javafx.stage.Modality; import javafx.stage.Stage; +import org.cryptomator.ui.common.DefaultSceneFactory; import org.cryptomator.ui.common.FXMLLoaderFactory; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxControllerKey; @@ -29,8 +30,8 @@ abstract class ForgetPasswordModule { @Provides @ForgetPasswordWindow @ForgetPasswordScoped - static FXMLLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, ResourceBundle resourceBundle) { - return new FXMLLoaderFactory(factories, resourceBundle); + static FXMLLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) { + return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle); } @Provides @@ -46,13 +47,6 @@ abstract class ForgetPasswordModule { return stage; } - @Provides - @FxmlScene(FxmlFile.FORGET_PASSWORD) - @ForgetPasswordScoped - static Scene provideForgetPasswordScene(@ForgetPasswordWindow FXMLLoaderFactory fxmlLoaders, @ForgetPasswordWindow Stage window) { - return fxmlLoaders.createScene("/fxml/forget_password.fxml"); - } - @Provides @ForgetPasswordWindow @ForgetPasswordScoped @@ -64,6 +58,15 @@ abstract class ForgetPasswordModule { @ForgetPasswordWindow @ForgetPasswordScoped abstract ReadOnlyBooleanProperty bindReadOnlyConfirmedProperty(@ForgetPasswordWindow BooleanProperty confirmedProperty); + + // ------------------ + + @Provides + @FxmlScene(FxmlFile.FORGET_PASSWORD) + @ForgetPasswordScoped + static Scene provideForgetPasswordScene(@ForgetPasswordWindow FXMLLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene("/fxml/forget_password.fxml"); + } // ------------------ diff --git a/main/ui/src/main/java/org/cryptomator/ui/forgetPassword/ForgetPasswordScoped.java b/main/ui/src/main/java/org/cryptomator/ui/forgetPassword/ForgetPasswordScoped.java index 388153e90..5be483753 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/forgetPassword/ForgetPasswordScoped.java +++ b/main/ui/src/main/java/org/cryptomator/ui/forgetPassword/ForgetPasswordScoped.java @@ -8,6 +8,6 @@ import java.lang.annotation.RetentionPolicy; @Scope @Documented @Retention(RetentionPolicy.RUNTIME) -public @interface ForgetPasswordScoped { +@interface ForgetPasswordScoped { } diff --git a/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java b/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java index 0935cbf1d..f52a9b2ed 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java +++ b/main/ui/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java @@ -26,8 +26,6 @@ import org.slf4j.LoggerFactory; import javax.inject.Inject; import java.awt.desktop.QuitResponse; import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; @FxApplicationScoped public class FxApplication extends Application { diff --git a/main/ui/src/main/java/org/cryptomator/ui/l10n/Localization.java b/main/ui/src/main/java/org/cryptomator/ui/l10n/Localization.java deleted file mode 100644 index 3ef4db621..000000000 --- a/main/ui/src/main/java/org/cryptomator/ui/l10n/Localization.java +++ /dev/null @@ -1,94 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2017 Skymatic UG (haftungsbeschränkt). - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the accompanying LICENSE file. - *******************************************************************************/ -package org.cryptomator.ui.l10n; - -import com.google.common.collect.Sets; -import org.apache.commons.lang3.StringUtils; -import org.cryptomator.ui.fxapp.FxApplicationScoped; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.inject.Inject; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.io.UncheckedIOException; -import java.nio.charset.StandardCharsets; -import java.util.Collection; -import java.util.Collections; -import java.util.Enumeration; -import java.util.Locale; -import java.util.Objects; -import java.util.PropertyResourceBundle; -import java.util.ResourceBundle; - -@FxApplicationScoped -public class Localization extends ResourceBundle { - - private static final Logger LOG = LoggerFactory.getLogger(Localization.class); - - private static final String LOCALIZATION_DEFAULT_FILE = "/localization/en.txt"; - private static final String LOCALIZATION_FILENAME_FMT = "/localization/%s.txt"; - - private final ResourceBundle fallback; - private final ResourceBundle localized; - - @Inject - public Localization() { - try { - this.fallback = Objects.requireNonNull(loadLocalizationFile(LOCALIZATION_DEFAULT_FILE)); - LOG.debug("Loaded localization default file: {}", LOCALIZATION_DEFAULT_FILE); - - String language = Locale.getDefault().getLanguage(); - String region = Locale.getDefault().getCountry(); - LOG.debug("Detected language \"{}\" and region \"{}\"", language, region); - - ResourceBundle localizationBundle = null; - if (StringUtils.isNotEmpty(language) && StringUtils.isNotEmpty(region)) { - String file = String.format(LOCALIZATION_FILENAME_FMT, language + "_" + region); - LOG.trace("Attempting to load localization from: {}", file); - localizationBundle = loadLocalizationFile(file); - } - if (StringUtils.isNotEmpty(language) && localizationBundle == null) { - String file = String.format(LOCALIZATION_FILENAME_FMT, language); - LOG.trace("Attempting to load localization from: {}", file); - localizationBundle = loadLocalizationFile(file); - } - if (localizationBundle == null) { - LOG.debug("No localization found. Falling back to default language."); - localizationBundle = this.fallback; - } - this.localized = Objects.requireNonNull(localizationBundle); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - // returns null if no resource for given path - private static ResourceBundle loadLocalizationFile(String resourcePath) throws IOException { - try (InputStream in = Localization.class.getResourceAsStream(resourcePath)) { - if (in != null) { - Reader reader = new InputStreamReader(in, StandardCharsets.UTF_8); - return new PropertyResourceBundle(reader); - } else { - return null; - } - } - } - - @Override - protected Object handleGetObject(String key) { - return localized.containsKey(key) ? localized.getObject(key) : fallback.getObject(key); - } - - @Override - public Enumeration getKeys() { - Collection keys = Sets.union(localized.keySet(), fallback.keySet()); - return Collections.enumeration(keys); - } - -} diff --git a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowController.java b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowController.java index 175de798e..f0acc0992 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowController.java @@ -10,7 +10,6 @@ import javafx.scene.layout.VBox; import javafx.stage.Stage; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultListManager; -import org.cryptomator.ui.common.FontLoader; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.fxapp.FxApplication; import org.cryptomator.ui.fxapp.UpdateChecker; @@ -21,18 +20,15 @@ import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Named; import java.io.File; -import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.util.Collection; -import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; @MainWindowScoped public class MainWindowController implements FxController { - private static final String TITLE_FONT = "/css/dosis-bold.ttf"; private static final Logger LOG = LoggerFactory.getLogger(MainWindowController.class); private static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; // TODO: deduplicate constant declared in multiple classes @@ -64,7 +60,6 @@ public class MainWindowController implements FxController { @FXML public void initialize() { LOG.debug("init MainWindowController"); - loadFont(TITLE_FONT); titleBar.setOnMousePressed(event -> { xOffset = event.getSceneX(); yOffset = event.getSceneY(); @@ -115,14 +110,6 @@ public class MainWindowController implements FxController { return Stream.empty(); } - private void loadFont(String resourcePath) { - try { - FontLoader.load(resourcePath); - } catch (FontLoader.FontLoaderException e) { - LOG.warn("Error loading font from path: " + resourcePath, e); - } - } - @FXML public void close() { if (minimizeToSysTray) { diff --git a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java index f9f479cc6..5ef76c5e0 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java +++ b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowModule.java @@ -12,6 +12,7 @@ import javafx.scene.input.KeyCombination; import javafx.stage.Stage; import javafx.stage.StageStyle; import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent; +import org.cryptomator.ui.common.DefaultSceneFactory; import org.cryptomator.ui.common.FXMLLoaderFactory; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxControllerKey; @@ -34,8 +35,8 @@ abstract class MainWindowModule { @Provides @MainWindow @MainWindowScoped - static FXMLLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, ResourceBundle resourceBundle) { - return new FXMLLoaderFactory(factories, resourceBundle); + static FXMLLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, MainWindowSceneFactory sceneFactory, ResourceBundle resourceBundle) { + return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle); } @Provides @@ -57,15 +58,7 @@ abstract class MainWindowModule { @FxmlScene(FxmlFile.MAIN_WINDOW) @MainWindowScoped static Scene provideMainScene(@MainWindow FXMLLoaderFactory fxmlLoaders, MainWindowController mainWindowController, VaultListController vaultListController) { - Scene scene = fxmlLoaders.createScene("/fxml/main_window.fxml"); - - // still not perfect... cant't we have a global menubar via the AWT tray app? - KeyCombination cmdN = new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN); - KeyCombination cmdW = new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN); - scene.getAccelerators().put(cmdN, vaultListController::didClickAddVault); - scene.getAccelerators().put(cmdW, mainWindowController::close); - - return scene; + return fxmlLoaders.createScene("/fxml/main_window.fxml"); } // ------------------ diff --git a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowSceneFactory.java b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowSceneFactory.java new file mode 100644 index 000000000..8c6486dc6 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/MainWindowSceneFactory.java @@ -0,0 +1,37 @@ +package org.cryptomator.ui.mainwindow; + +import dagger.Lazy; +import javafx.scene.Scene; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.input.KeyCombination; +import javafx.stage.Stage; +import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.ui.common.DefaultSceneFactory; + +import javax.inject.Inject; + +@MainWindowScoped +public class MainWindowSceneFactory extends DefaultSceneFactory { + + protected static final KeyCodeCombination SHORTCUT_N = new KeyCodeCombination(KeyCode.N, KeyCombination.SHORTCUT_DOWN); + + private final Lazy mainWindowController; + private final Lazy vaultListController; + + @Inject + public MainWindowSceneFactory(Lazy mainWindowController, Lazy vaultListController) { + this.mainWindowController = mainWindowController; + this.vaultListController = vaultListController; + } + + @Override + protected void setupDefaultAccelerators(Scene scene, Stage stage) { + if (SystemUtils.IS_OS_WINDOWS) { + scene.getAccelerators().put(ALT_F4, mainWindowController.get()::close); + } else { + scene.getAccelerators().put(SHORTCUT_W, mainWindowController.get()::close); + } + scene.getAccelerators().put(SHORTCUT_N, vaultListController.get()::didClickAddVault); + } +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java index b43577ec1..82fe7c7c7 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java @@ -17,6 +17,7 @@ import org.slf4j.LoggerFactory; import javax.inject.Inject; +//TODO: Add check if a vault in the list is invalid and add notification & controller @MainWindowScoped public class VaultListController implements FxController { @@ -53,10 +54,6 @@ public class VaultListController implements FxController { if (c.wasAdded()) { Vault anyAddedVault = c.getAddedSubList().get(0); vaultList.getSelectionModel().select(anyAddedVault); - window.setIconified(false); - window.show(); - window.toFront(); - window.requestFocus(); // TODO: this beeps on macOS if there is a modal child window... } } }); diff --git a/main/ui/src/main/java/org/cryptomator/ui/migration/MigrationModule.java b/main/ui/src/main/java/org/cryptomator/ui/migration/MigrationModule.java index 25cc31206..11d13fa81 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/migration/MigrationModule.java +++ b/main/ui/src/main/java/org/cryptomator/ui/migration/MigrationModule.java @@ -1,19 +1,14 @@ package org.cryptomator.ui.migration; import dagger.Binds; -import dagger.Lazy; import dagger.Module; import dagger.Provides; import dagger.multibindings.IntoMap; -import dagger.multibindings.IntoSet; import javafx.scene.Scene; import javafx.scene.image.Image; -import javafx.scene.input.KeyCode; -import javafx.scene.input.KeyCodeCombination; -import javafx.scene.input.KeyCombination; import javafx.stage.Modality; import javafx.stage.Stage; -import org.apache.commons.lang3.SystemUtils; +import org.cryptomator.ui.common.DefaultSceneFactory; import org.cryptomator.ui.common.FXMLLoaderFactory; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxControllerKey; @@ -26,7 +21,6 @@ import javax.inject.Provider; import java.util.Map; import java.util.Optional; import java.util.ResourceBundle; -import java.util.Set; @Module abstract class MigrationModule { @@ -34,29 +28,19 @@ abstract class MigrationModule { @Provides @MigrationWindow @MigrationScoped - static FXMLLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, ResourceBundle resourceBundle) { - return new FXMLLoaderFactory(factories, resourceBundle); - } - - @Provides - @MigrationWindow - @MigrationScoped - static Map provideDefaultAccellerators(@MigrationWindow Set> accelerators) { - return Map.ofEntries(accelerators.toArray(Map.Entry[]::new)); + static FXMLLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) { + return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle); } @Provides @MigrationWindow @MigrationScoped - static Stage provideStage(@MainWindow Stage owner, ResourceBundle resourceBundle, @Named("windowIcon") Optional windowIcon, @MigrationWindow Lazy> accelerators) { + static Stage provideStage(@MainWindow Stage owner, ResourceBundle resourceBundle, @Named("windowIcon") Optional windowIcon) { Stage stage = new Stage(); stage.setTitle(resourceBundle.getString("migration.title")); stage.setResizable(false); stage.initModality(Modality.WINDOW_MODAL); stage.initOwner(owner); - stage.sceneProperty().addListener(observable -> { - stage.getScene().getAccelerators().putAll(accelerators.get()); - }); windowIcon.ifPresent(stage.getIcons()::add); return stage; } @@ -84,19 +68,6 @@ abstract class MigrationModule { // ------------------ - @Provides - @IntoSet - @MigrationWindow - static Map.Entry provideCloseWindowShortcut(@MigrationWindow Stage window) { - if (SystemUtils.IS_OS_WINDOWS) { - return Map.entry(new KeyCodeCombination(KeyCode.F4, KeyCombination.ALT_DOWN), window::close); - } else { - return Map.entry(new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN), window::close); - } - } - - // ------------------ - @Binds @IntoMap @FxControllerKey(MigrationStartController.class) diff --git a/main/ui/src/main/java/org/cryptomator/ui/migration/MigrationRunController.java b/main/ui/src/main/java/org/cryptomator/ui/migration/MigrationRunController.java index b7e0d6eaa..181876d2f 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/migration/MigrationRunController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/migration/MigrationRunController.java @@ -7,8 +7,13 @@ import javafx.animation.Timeline; import javafx.beans.binding.Bindings; import javafx.beans.binding.ObjectBinding; import javafx.beans.property.BooleanProperty; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.ReadOnlyDoubleProperty; import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.value.WritableValue; +import javafx.concurrent.ScheduledService; +import javafx.concurrent.Task; import javafx.fxml.FXML; import javafx.scene.Scene; import javafx.scene.control.ContentDisplay; @@ -17,6 +22,7 @@ import javafx.util.Duration; import org.cryptomator.common.vaults.Vault; import org.cryptomator.common.vaults.VaultState; import org.cryptomator.cryptofs.migration.Migrators; +import org.cryptomator.cryptofs.migration.api.MigrationProgressListener; import org.cryptomator.cryptofs.migration.api.NoApplicableMigratorException; import org.cryptomator.cryptolib.api.InvalidPassphraseException; import org.cryptomator.keychain.KeychainAccess; @@ -39,6 +45,7 @@ public class MigrationRunController implements FxController { private static final Logger LOG = LoggerFactory.getLogger(MigrationRunController.class); private static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; // TODO: deduplicate constant declared in multiple classes + private static final Duration MIGRATION_PROGRESS_UPDATE_INTERVAL = Duration.millis(25); private final Stage window; private final Vault vault; @@ -48,6 +55,9 @@ public class MigrationRunController implements FxController { private final Lazy successScene; private final ObjectBinding migrateButtonContentDisplay; private final BooleanProperty migrationButtonDisabled; + private final DoubleProperty migrationProgress; + private final ScheduledService migrationProgressObservationService; + private volatile double volatileMigrationProgress = -1.0; public NiceSecurePasswordField passwordField; @Inject @@ -60,6 +70,10 @@ public class MigrationRunController implements FxController { this.successScene = successScene; this.migrateButtonContentDisplay = Bindings.createObjectBinding(this::getMigrateButtonContentDisplay, vault.stateProperty()); this.migrationButtonDisabled = new SimpleBooleanProperty(); + this.migrationProgress = new SimpleDoubleProperty(volatileMigrationProgress); + this.migrationProgressObservationService = new MigrationProgressObservationService(); + migrationProgressObservationService.setExecutor(executor); + migrationProgressObservationService.setPeriod(MIGRATION_PROGRESS_UPDATE_INTERVAL); } public void initialize() { @@ -79,17 +93,18 @@ public class MigrationRunController implements FxController { LOG.info("Migrating vault {}", vault.getPath()); CharSequence password = passwordField.getCharacters(); vault.setState(VaultState.PROCESSING); + migrationProgressObservationService.start(); Tasks.create(() -> { Migrators migrators = Migrators.get(); - migrators.migrate(vault.getPath(), MASTERKEY_FILENAME, password); + migrators.migrate(vault.getPath(), MASTERKEY_FILENAME, password, this::migrationProgressChanged); return migrators.needsMigration(vault.getPath(), MASTERKEY_FILENAME); }).onSuccess(needsAnotherMigration -> { + LOG.info("Migration of '{}' succeeded.", vault.getDisplayableName()); if (needsAnotherMigration) { vault.setState(VaultState.NEEDS_MIGRATION); } else { vault.setState(VaultState.LOCKED); passwordField.swipe(); - LOG.info("Migration of '{}' succeeded.", vault.getDisplayableName()); window.setScene(successScene.get()); } }).onError(InvalidPassphraseException.class, e -> { @@ -105,9 +120,29 @@ public class MigrationRunController implements FxController { LOG.error("Migration failed for technical reasons.", e); vault.setState(VaultState.ERROR); // TODO show generic error screen + }).andFinally(() -> { + migrationProgressObservationService.cancel(); + migrationProgressObservationService.reset(); }).runOnce(executor); } + // Called by a background task. We can not directly modify observable properties from here + private void migrationProgressChanged(MigrationProgressListener.ProgressState state, double progress) { + switch (state) { + case INITIALIZING: + volatileMigrationProgress = -1.0; + break; + case MIGRATING: + volatileMigrationProgress = progress; + break; + case FINALIZING: + volatileMigrationProgress = 1.0; + break; + default: + throw new IllegalStateException("Unexpted state " + state); + } + } + private void loadStoredPassword() { assert keychainAccess.isPresent(); char[] storedPw = null; @@ -126,6 +161,27 @@ public class MigrationRunController implements FxController { } } + // Sets migrationProgress to volatileMigrationProgress at its configured interval + private class MigrationProgressObservationService extends ScheduledService { + + @Override + protected Task createTask() { + return new Task<>() { + @Override + protected Double call() { + return volatileMigrationProgress; + } + }; + } + + @Override + protected void succeeded() { + assert getValue() != null; + migrationProgress.set(getValue()); + super.succeeded(); + } + } + /* Animations */ private void shakeWindow() { @@ -180,4 +236,12 @@ public class MigrationRunController implements FxController { } } + public ReadOnlyDoubleProperty migrationProgressProperty() { + return migrationProgress; + } + + public double getMigrationProgress() { + return migrationProgress.get(); + } + } diff --git a/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java b/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java index 44913e8f4..ae4233cb8 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java +++ b/main/ui/src/main/java/org/cryptomator/ui/preferences/PreferencesModule.java @@ -6,10 +6,8 @@ import dagger.Provides; import dagger.multibindings.IntoMap; import javafx.scene.Scene; import javafx.scene.image.Image; -import javafx.scene.input.KeyCode; -import javafx.scene.input.KeyCodeCombination; -import javafx.scene.input.KeyCombination; import javafx.stage.Stage; +import org.cryptomator.ui.common.DefaultSceneFactory; import org.cryptomator.ui.common.FXMLLoaderFactory; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxControllerKey; @@ -28,8 +26,8 @@ abstract class PreferencesModule { @Provides @PreferencesWindow @PreferencesScoped - static FXMLLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, ResourceBundle resourceBundle) { - return new FXMLLoaderFactory(factories, resourceBundle); + static FXMLLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) { + return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle); } @Provides @@ -46,13 +44,8 @@ abstract class PreferencesModule { @Provides @FxmlScene(FxmlFile.PREFERENCES) @PreferencesScoped - static Scene providePreferencesScene(@PreferencesWindow FXMLLoaderFactory fxmlLoaders, @PreferencesWindow Stage window) { - Scene scene = fxmlLoaders.createScene("/fxml/preferences.fxml"); - - KeyCombination cmdW = new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN); - scene.getAccelerators().put(cmdW, window::close); - - return scene; + static Scene providePreferencesScene(@PreferencesWindow FXMLLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene("/fxml/preferences.fxml"); } // ------------------ diff --git a/main/ui/src/main/java/org/cryptomator/ui/quit/QuitModule.java b/main/ui/src/main/java/org/cryptomator/ui/quit/QuitModule.java index 5142d13c1..5383a0f5e 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/quit/QuitModule.java +++ b/main/ui/src/main/java/org/cryptomator/ui/quit/QuitModule.java @@ -11,6 +11,7 @@ import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCombination; import javafx.stage.Modality; import javafx.stage.Stage; +import org.cryptomator.ui.common.DefaultSceneFactory; import org.cryptomator.ui.common.FXMLLoaderFactory; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxControllerKey; @@ -29,8 +30,8 @@ abstract class QuitModule { @Provides @QuitWindow @QuitScoped - static FXMLLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, ResourceBundle resourceBundle) { - return new FXMLLoaderFactory(factories, resourceBundle); + static FXMLLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) { + return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle); } @Provides @@ -48,13 +49,8 @@ abstract class QuitModule { @Provides @FxmlScene(FxmlFile.QUIT) @QuitScoped - static Scene provideUnlockScene(@QuitWindow FXMLLoaderFactory fxmlLoaders, @QuitWindow Stage window) { - Scene scene = fxmlLoaders.createScene("/fxml/quit.fxml"); - - KeyCombination cmdW = new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN); - scene.getAccelerators().put(cmdW, window::close); - - return scene; + static Scene provideUnlockScene(@QuitWindow FXMLLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene("/fxml/quit.fxml"); } // ------------------ diff --git a/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java b/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java new file mode 100644 index 000000000..25fa319b4 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyComponent.java @@ -0,0 +1,45 @@ +package org.cryptomator.ui.recoverykey; + +import dagger.BindsInstance; +import dagger.Lazy; +import dagger.Subcomponent; +import javafx.scene.Scene; +import javafx.stage.Stage; +import org.cryptomator.common.vaults.Vault; +import org.cryptomator.ui.common.FxmlFile; +import org.cryptomator.ui.common.FxmlScene; + +import javax.annotation.Nullable; +import javax.inject.Named; +import java.util.Optional; + +@RecoveryKeyScoped +@Subcomponent(modules = {RecoveryKeyModule.class}) +public interface RecoveryKeyComponent { + + @RecoveryKeyWindow + Stage window(); + + @FxmlScene(FxmlFile.RECOVERYKEY_CREATE) + Lazy scene(); + + default void showRecoveryKeyCreationWindow() { + Stage stage = window(); + stage.setScene(scene().get()); + stage.sizeToScene(); + stage.show(); + } + + @Subcomponent.Builder + interface Builder { + + @BindsInstance + Builder vault(@RecoveryKeyWindow Vault vault); + + @BindsInstance + Builder owner(@Named("keyRecoveryOwner") Stage owner); + + RecoveryKeyComponent build(); + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java b/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java new file mode 100644 index 000000000..2ca6ebb69 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyCreationController.java @@ -0,0 +1,103 @@ +package org.cryptomator.ui.recoverykey; + +import dagger.Lazy; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.beans.property.ReadOnlyStringProperty; +import javafx.beans.property.StringProperty; +import javafx.beans.value.WritableValue; +import javafx.fxml.FXML; +import javafx.scene.Scene; +import javafx.stage.Stage; +import javafx.util.Duration; +import org.cryptomator.common.vaults.Vault; +import org.cryptomator.cryptolib.api.InvalidPassphraseException; +import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.common.FxmlFile; +import org.cryptomator.ui.common.FxmlScene; +import org.cryptomator.ui.common.Tasks; +import org.cryptomator.ui.controls.NiceSecurePasswordField; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; +import javax.inject.Inject; +import java.io.IOException; +import java.util.concurrent.ExecutorService; + +@RecoveryKeyScoped +public class RecoveryKeyCreationController implements FxController { + + private static final Logger LOG = LoggerFactory.getLogger(RecoveryKeyCreationController.class); + + private final Stage window; + private final Lazy successScene; + private final Vault vault; + private final ExecutorService executor; + private final RecoveryKeyFactory recoveryKeyFactory; + private final StringProperty recoveryKeyProperty; + public NiceSecurePasswordField passwordField; + + @Inject + public RecoveryKeyCreationController(@RecoveryKeyWindow Stage window, @FxmlScene(FxmlFile.RECOVERYKEY_DISPLAY) Lazy successScene, @RecoveryKeyWindow Vault vault, RecoveryKeyFactory recoveryKeyFactory, ExecutorService executor, @RecoveryKeyWindow StringProperty recoveryKey) { + this.window = window; + this.successScene = successScene; + this.vault = vault; + this.executor = executor; + this.recoveryKeyFactory = recoveryKeyFactory; + this.recoveryKeyProperty = recoveryKey; + } + + @FXML + public void createRecoveryKey() { + Tasks.create(() -> { + return recoveryKeyFactory.createRecoveryKey(vault.getPath(), passwordField.getCharacters()); + }).onSuccess(result -> { + recoveryKeyProperty.set(result); + window.setScene(successScene.get()); + }).onError(IOException.class, e -> { + LOG.error("Creation of recovery key failed.", e); + }).onError(InvalidPassphraseException.class, e -> { + shakeWindow(); + }).runOnce(executor); + } + + @FXML + public void close() { + window.close(); + } + + /* Animations */ + + private void shakeWindow() { + WritableValue writableWindowX = new WritableValue<>() { + @Override + public Double getValue() { + return window.getX(); + } + + @Override + public void setValue(Double value) { + window.setX(value); + } + }; + Timeline timeline = new Timeline( // + new KeyFrame(Duration.ZERO, new KeyValue(writableWindowX, window.getX())), // + new KeyFrame(new Duration(100), new KeyValue(writableWindowX, window.getX() - 22.0)), // + new KeyFrame(new Duration(200), new KeyValue(writableWindowX, window.getX() + 18.0)), // + new KeyFrame(new Duration(300), new KeyValue(writableWindowX, window.getX() - 14.0)), // + new KeyFrame(new Duration(400), new KeyValue(writableWindowX, window.getX() + 10.0)), // + new KeyFrame(new Duration(500), new KeyValue(writableWindowX, window.getX() - 6.0)), // + new KeyFrame(new Duration(600), new KeyValue(writableWindowX, window.getX() + 2.0)), // + new KeyFrame(new Duration(700), new KeyValue(writableWindowX, window.getX())) // + ); + timeline.play(); + } + + /* Getter/Setter */ + + public Vault getVault() { + return vault; + } +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyDisplayController.java b/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyDisplayController.java new file mode 100644 index 000000000..2d96d786f --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyDisplayController.java @@ -0,0 +1,44 @@ +package org.cryptomator.ui.recoverykey; + +import javafx.beans.property.ReadOnlyStringProperty; +import javafx.beans.property.StringProperty; +import javafx.fxml.FXML; +import javafx.stage.Stage; +import org.cryptomator.common.vaults.Vault; +import org.cryptomator.ui.common.FxController; + +import javax.inject.Inject; + +@RecoveryKeyScoped +public class RecoveryKeyDisplayController implements FxController { + + private final Stage window; + private final Vault vault; + private final StringProperty recoveryKeyProperty; + + @Inject + public RecoveryKeyDisplayController(@RecoveryKeyWindow Stage window, @RecoveryKeyWindow Vault vault, @RecoveryKeyWindow StringProperty recoveryKey) { + this.window = window; + this.vault = vault; + this.recoveryKeyProperty = recoveryKey; + } + + @FXML + public void close() { + window.close(); + } + + /* Getter/Setter */ + + public Vault getVault() { + return vault; + } + + public ReadOnlyStringProperty recoveryKeyProperty() { + return recoveryKeyProperty; + } + + public String getRecoveryKey() { + return recoveryKeyProperty.get(); + } +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyFactory.java b/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyFactory.java new file mode 100644 index 000000000..00e4002e2 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyFactory.java @@ -0,0 +1,78 @@ +package org.cryptomator.ui.recoverykey; + +import com.google.common.base.Preconditions; +import com.google.common.hash.Hashing; +import org.cryptomator.cryptofs.CryptoFileSystemProvider; +import org.cryptomator.cryptolib.api.InvalidPassphraseException; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Arrays; + +@Singleton +public class RecoveryKeyFactory { + + private static final String MASTERKEY_FILENAME = "masterkey.cryptomator"; // TODO: deduplicate constant declared in multiple classes + + private final WordEncoder wordEncoder; + + @Inject + public RecoveryKeyFactory(WordEncoder wordEncoder) { + this.wordEncoder = wordEncoder; + } + + /** + * @param vaultPath Path to the storage location of a vault + * @param password The vault's password + * @return The recovery key of the vault at the given path + * @throws IOException If the masterkey file could not be read + * @throws InvalidPassphraseException If the provided password is wrong + * @apiNote This is a long-running operation and should be invoked in a background thread + */ + public String createRecoveryKey(Path vaultPath, CharSequence password) throws IOException, InvalidPassphraseException { + byte[] rawKey = CryptoFileSystemProvider.exportRawKey(vaultPath, MASTERKEY_FILENAME, new byte[0], password); + try { + return createRecoveryKey(rawKey); + } finally { + Arrays.fill(rawKey, (byte) 0x00); + } + } + + // visible for testing + String createRecoveryKey(byte[] rawKey) { + Preconditions.checkArgument(rawKey.length == 64, "key should be 64 bytes"); + byte[] paddedKey = Arrays.copyOf(rawKey, 66); + try { + // copy 16 most significant bits of CRC32(rawKey) to the end of paddedKey: + Hashing.crc32().hashBytes(rawKey).writeBytesTo(paddedKey, 64, 2); + return wordEncoder.encodePadded(paddedKey); + } finally { + Arrays.fill(paddedKey, (byte) 0x00); + } + } + + /** + * Checks whether a String is a syntactically correct recovery key with a valid checksum + * @param recoveryKey A word sequence which might be a recovery key + * @return true if this seems to be a legitimate recovery key + */ + public boolean validateRecoveryKey(String recoveryKey) { + final byte[] paddedKey; + try { + paddedKey = wordEncoder.decode(recoveryKey); + } catch (IllegalArgumentException e) { + return false; + } + if (paddedKey.length != 66) { + return false; + } + byte[] rawKey = Arrays.copyOf(paddedKey, 64); + byte[] expectedCrc16 = Arrays.copyOfRange(paddedKey, 64, 66); + byte[] actualCrc32 = Hashing.crc32().hashBytes(rawKey).asBytes(); + byte[] actualCrc16 = Arrays.copyOf(actualCrc32, 2); + return Arrays.equals(expectedCrc16, actualCrc16); + } + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java b/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java new file mode 100644 index 000000000..261b50682 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyModule.java @@ -0,0 +1,84 @@ +package org.cryptomator.ui.recoverykey; + +import dagger.Binds; +import dagger.Module; +import dagger.Provides; +import dagger.multibindings.IntoMap; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.scene.Scene; +import javafx.scene.image.Image; +import javafx.stage.Modality; +import javafx.stage.Stage; +import org.cryptomator.ui.common.DefaultSceneFactory; +import org.cryptomator.ui.common.FXMLLoaderFactory; +import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.common.FxControllerKey; +import org.cryptomator.ui.common.FxmlFile; +import org.cryptomator.ui.common.FxmlScene; + +import javax.inject.Named; +import javax.inject.Provider; +import java.util.Map; +import java.util.Optional; +import java.util.ResourceBundle; + +@Module +abstract class RecoveryKeyModule { + + @Provides + @RecoveryKeyWindow + @RecoveryKeyScoped + static FXMLLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) { + return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle); + } + + @Provides + @RecoveryKeyWindow + @RecoveryKeyScoped + static Stage provideStage(ResourceBundle resourceBundle, @Named("windowIcon") Optional windowIcon, @Named("keyRecoveryOwner") Stage owner) { + Stage stage = new Stage(); + stage.setTitle(resourceBundle.getString("recoveryKey.title")); + stage.setResizable(false); + stage.initModality(Modality.WINDOW_MODAL); + stage.initOwner(owner); + windowIcon.ifPresent(stage.getIcons()::add); + return stage; + } + + @Provides + @RecoveryKeyWindow + @RecoveryKeyScoped + static StringProperty provideRecoveryKeyProperty() { + return new SimpleStringProperty(); + } + + // ------------------ + + @Provides + @FxmlScene(FxmlFile.RECOVERYKEY_CREATE) + @RecoveryKeyScoped + static Scene provideRecoveryKeyCreationScene(@RecoveryKeyWindow FXMLLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene("/fxml/recoverykey_create.fxml"); + } + + @Provides + @FxmlScene(FxmlFile.RECOVERYKEY_DISPLAY) + @RecoveryKeyScoped + static Scene provideRecoveryKeyDisplayScene(@RecoveryKeyWindow FXMLLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene("/fxml/recoverykey_display.fxml"); + } + + // ------------------ + + @Binds + @IntoMap + @FxControllerKey(RecoveryKeyCreationController.class) + abstract FxController bindRecoveryKeyCreationController(RecoveryKeyCreationController controller); + + @Binds + @IntoMap + @FxControllerKey(RecoveryKeyDisplayController.class) + abstract FxController bindRecoveryKeyDisplayController(RecoveryKeyDisplayController controller); + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyScoped.java b/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyScoped.java new file mode 100644 index 000000000..e7b266605 --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyScoped.java @@ -0,0 +1,13 @@ +package org.cryptomator.ui.recoverykey; + +import javax.inject.Scope; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Scope +@Documented +@Retention(RetentionPolicy.RUNTIME) +public @interface RecoveryKeyScoped { + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyWindow.java b/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyWindow.java new file mode 100644 index 000000000..d43fb7b3a --- /dev/null +++ b/main/ui/src/main/java/org/cryptomator/ui/recoverykey/RecoveryKeyWindow.java @@ -0,0 +1,14 @@ +package org.cryptomator.ui.recoverykey; + +import javax.inject.Qualifier; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Qualifier +@Documented +@Retention(RUNTIME) +@interface RecoveryKeyWindow { + +} diff --git a/main/ui/src/main/java/org/cryptomator/ui/keyrecovery/WordEncoder.java b/main/ui/src/main/java/org/cryptomator/ui/recoverykey/WordEncoder.java similarity index 94% rename from main/ui/src/main/java/org/cryptomator/ui/keyrecovery/WordEncoder.java rename to main/ui/src/main/java/org/cryptomator/ui/recoverykey/WordEncoder.java index d5d3dc091..b06d06902 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/keyrecovery/WordEncoder.java +++ b/main/ui/src/main/java/org/cryptomator/ui/recoverykey/WordEncoder.java @@ -1,8 +1,10 @@ -package org.cryptomator.ui.keyrecovery; +package org.cryptomator.ui.recoverykey; import com.google.common.base.Preconditions; import com.google.common.base.Splitter; +import javax.inject.Inject; +import javax.inject.Singleton; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -14,16 +16,19 @@ import java.util.Map; import java.util.stream.Collectors; import java.util.stream.IntStream; +@Singleton class WordEncoder { + private static final String DEFAULT_WORD_FILE = "/i18n/4096words_en.txt"; private static final int WORD_COUNT = 4096; private static final char DELIMITER = ' '; private final List words; private final Map indices; + @Inject public WordEncoder() { - this("/i18n/4096words_en.txt"); + this(DEFAULT_WORD_FILE); } public WordEncoder(String wordFile) { diff --git a/main/ui/src/main/java/org/cryptomator/ui/removevault/RemoveVaultModule.java b/main/ui/src/main/java/org/cryptomator/ui/removevault/RemoveVaultModule.java index 60d541704..965b6c6e8 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/removevault/RemoveVaultModule.java +++ b/main/ui/src/main/java/org/cryptomator/ui/removevault/RemoveVaultModule.java @@ -11,6 +11,7 @@ import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCombination; import javafx.stage.Modality; import javafx.stage.Stage; +import org.cryptomator.ui.common.DefaultSceneFactory; import org.cryptomator.ui.common.FXMLLoaderFactory; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxControllerKey; @@ -30,8 +31,8 @@ abstract class RemoveVaultModule { @Provides @RemoveVaultWindow @RemoveVaultScoped - static FXMLLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, ResourceBundle resourceBundle) { - return new FXMLLoaderFactory(factories, resourceBundle); + static FXMLLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) { + return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle); } @Provides @@ -50,13 +51,8 @@ abstract class RemoveVaultModule { @Provides @FxmlScene(FxmlFile.REMOVE_VAULT) @RemoveVaultScoped - static Scene provideRemoveVaultScene(@RemoveVaultWindow FXMLLoaderFactory fxmlLoaders, @RemoveVaultWindow Stage window) { - Scene scene = fxmlLoaders.createScene("/fxml/remove_vault.fxml"); - - KeyCombination cmdW = new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN); - scene.getAccelerators().put(cmdW, window::close); - - return scene; + static Scene provideRemoveVaultScene(@RemoveVaultWindow FXMLLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene("/fxml/remove_vault.fxml"); } // ------------------ diff --git a/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java b/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java index 7ee1f71e3..488f7cfa8 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java +++ b/main/ui/src/main/java/org/cryptomator/ui/unlock/UnlockModule.java @@ -6,11 +6,9 @@ import dagger.Provides; import dagger.multibindings.IntoMap; import javafx.scene.Scene; import javafx.scene.image.Image; -import javafx.scene.input.KeyCode; -import javafx.scene.input.KeyCodeCombination; -import javafx.scene.input.KeyCombination; import javafx.stage.Modality; import javafx.stage.Stage; +import org.cryptomator.ui.common.DefaultSceneFactory; import org.cryptomator.ui.common.FXMLLoaderFactory; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxControllerKey; @@ -24,14 +22,14 @@ import java.util.Map; import java.util.Optional; import java.util.ResourceBundle; -@Module(subcomponents={ForgetPasswordComponent.class}) +@Module(subcomponents = {ForgetPasswordComponent.class}) abstract class UnlockModule { @Provides @UnlockWindow @UnlockScoped - static FXMLLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, ResourceBundle resourceBundle) { - return new FXMLLoaderFactory(factories, resourceBundle); + static FXMLLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) { + return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle); } @Provides @@ -49,25 +47,15 @@ abstract class UnlockModule { @Provides @FxmlScene(FxmlFile.UNLOCK) @UnlockScoped - static Scene provideUnlockScene(@UnlockWindow FXMLLoaderFactory fxmlLoaders, @UnlockWindow Stage window) { - Scene scene = fxmlLoaders.createScene("/fxml/unlock2.fxml"); // TODO rename fxml file - - KeyCombination cmdW = new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN); - scene.getAccelerators().put(cmdW, window::close); - - return scene; + static Scene provideUnlockScene(@UnlockWindow FXMLLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene("/fxml/unlock.fxml"); } @Provides @FxmlScene(FxmlFile.UNLOCK_SUCCESS) @UnlockScoped - static Scene provideUnlockSuccessScene(@UnlockWindow FXMLLoaderFactory fxmlLoaders, @UnlockWindow Stage window) { - Scene scene = fxmlLoaders.createScene("/fxml/unlock_success.fxml"); - - KeyCombination cmdW = new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN); - scene.getAccelerators().put(cmdW, window::close); - - return scene; + static Scene provideUnlockSuccessScene(@UnlockWindow FXMLLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene("/fxml/unlock_success.fxml"); } diff --git a/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/GeneralVaultOptionsController.java b/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/GeneralVaultOptionsController.java index cf8a804c6..5e68480a8 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/GeneralVaultOptionsController.java +++ b/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/GeneralVaultOptionsController.java @@ -5,6 +5,7 @@ import javafx.stage.Stage; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.changepassword.ChangePasswordComponent; import org.cryptomator.ui.common.FxController; +import org.cryptomator.ui.recoverykey.RecoveryKeyComponent; import javax.inject.Inject; @@ -14,12 +15,14 @@ public class GeneralVaultOptionsController implements FxController { private final Vault vault; private final Stage window; private final ChangePasswordComponent.Builder changePasswordWindow; + private final RecoveryKeyComponent.Builder recoveryKeyWindow; @Inject - GeneralVaultOptionsController(@VaultOptionsWindow Vault vault, @VaultOptionsWindow Stage window, ChangePasswordComponent.Builder changePasswordWindow) { + GeneralVaultOptionsController(@VaultOptionsWindow Vault vault, @VaultOptionsWindow Stage window, ChangePasswordComponent.Builder changePasswordWindow, RecoveryKeyComponent.Builder recoveryKeyWindow) { this.vault = vault; this.window = window; this.changePasswordWindow = changePasswordWindow; + this.recoveryKeyWindow = recoveryKeyWindow; } @FXML @@ -27,4 +30,9 @@ public class GeneralVaultOptionsController implements FxController { changePasswordWindow.vault(vault).owner(window).build().showChangePasswordWindow(); } + @FXML + public void showRecoveryKey() { + recoveryKeyWindow.vault(vault).owner(window).build().showRecoveryKeyCreationWindow(); + } + } diff --git a/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsModule.java b/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsModule.java index 967bbbe1a..09185729a 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsModule.java +++ b/main/ui/src/main/java/org/cryptomator/ui/vaultoptions/VaultOptionsModule.java @@ -6,20 +6,18 @@ import dagger.Provides; import dagger.multibindings.IntoMap; import javafx.scene.Scene; import javafx.scene.image.Image; -import javafx.scene.input.KeyCode; -import javafx.scene.input.KeyCodeCombination; -import javafx.scene.input.KeyCombination; import javafx.stage.Modality; import javafx.stage.Stage; -import javafx.stage.StageStyle; import org.cryptomator.common.vaults.Vault; import org.cryptomator.ui.changepassword.ChangePasswordComponent; +import org.cryptomator.ui.common.DefaultSceneFactory; import org.cryptomator.ui.common.FXMLLoaderFactory; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxControllerKey; import org.cryptomator.ui.common.FxmlFile; import org.cryptomator.ui.common.FxmlScene; import org.cryptomator.ui.mainwindow.MainWindow; +import org.cryptomator.ui.recoverykey.RecoveryKeyComponent; import javax.inject.Named; import javax.inject.Provider; @@ -27,14 +25,14 @@ import java.util.Map; import java.util.Optional; import java.util.ResourceBundle; -@Module(subcomponents = {ChangePasswordComponent.class}) +@Module(subcomponents = {ChangePasswordComponent.class, RecoveryKeyComponent.class}) abstract class VaultOptionsModule { @Provides @VaultOptionsWindow @VaultOptionsScoped - static FXMLLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, ResourceBundle resourceBundle) { - return new FXMLLoaderFactory(factories, resourceBundle); + static FXMLLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) { + return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle); } @Provides @@ -53,13 +51,8 @@ abstract class VaultOptionsModule { @Provides @FxmlScene(FxmlFile.VAULT_OPTIONS) @VaultOptionsScoped - static Scene provideVaultOptionsScene(@VaultOptionsWindow FXMLLoaderFactory fxmlLoaders, @VaultOptionsWindow Stage window) { - Scene scene = fxmlLoaders.createScene("/fxml/vault_options.fxml"); - - KeyCombination cmdW = new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN); - scene.getAccelerators().put(cmdW, window::close); - - return scene; + static Scene provideVaultOptionsScene(@VaultOptionsWindow FXMLLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene("/fxml/vault_options.fxml"); } // ------------------ diff --git a/main/ui/src/main/java/org/cryptomator/ui/wrongfilealert/WrongFileAlertModule.java b/main/ui/src/main/java/org/cryptomator/ui/wrongfilealert/WrongFileAlertModule.java index 174db9805..c0f2786dc 100644 --- a/main/ui/src/main/java/org/cryptomator/ui/wrongfilealert/WrongFileAlertModule.java +++ b/main/ui/src/main/java/org/cryptomator/ui/wrongfilealert/WrongFileAlertModule.java @@ -6,11 +6,9 @@ import dagger.Provides; import dagger.multibindings.IntoMap; import javafx.scene.Scene; import javafx.scene.image.Image; -import javafx.scene.input.KeyCode; -import javafx.scene.input.KeyCodeCombination; -import javafx.scene.input.KeyCombination; import javafx.stage.Modality; import javafx.stage.Stage; +import org.cryptomator.ui.common.DefaultSceneFactory; import org.cryptomator.ui.common.FXMLLoaderFactory; import org.cryptomator.ui.common.FxController; import org.cryptomator.ui.common.FxControllerKey; @@ -29,8 +27,8 @@ abstract class WrongFileAlertModule { @Provides @WrongFileAlertWindow @WrongFileAlertScoped - static FXMLLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, ResourceBundle resourceBundle) { - return new FXMLLoaderFactory(factories, resourceBundle); + static FXMLLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) { + return new FXMLLoaderFactory(factories, sceneFactory, resourceBundle); } @Provides @@ -48,13 +46,8 @@ abstract class WrongFileAlertModule { @Provides @FxmlScene(FxmlFile.WRONGFILEALERT) @WrongFileAlertScoped - static Scene provideWrongFileAlertScene(@WrongFileAlertWindow FXMLLoaderFactory fxmlLoaders, @WrongFileAlertWindow Stage window) { - Scene scene = fxmlLoaders.createScene("/fxml/wrongfilealert.fxml"); // TODO rename fxml file - - KeyCombination cmdW = new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN); - scene.getAccelerators().put(cmdW, window::close); - - return scene; + static Scene provideWrongFileAlertScene(@WrongFileAlertWindow FXMLLoaderFactory fxmlLoaders) { + return fxmlLoaders.createScene("/fxml/wrongfilealert.fxml"); } // ------------------ diff --git a/main/ui/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory b/main/ui/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory deleted file mode 100644 index 7a84fb0a0..000000000 --- a/main/ui/src/main/resources/META-INF/services/javax.script.ScriptEngineFactory +++ /dev/null @@ -1 +0,0 @@ -apple.applescript.AppleScriptEngineFactory \ No newline at end of file diff --git a/main/ui/src/main/resources/css/dark_theme.css b/main/ui/src/main/resources/css/dark_theme.css index 727fbb01c..c277fc09f 100644 --- a/main/ui/src/main/resources/css/dark_theme.css +++ b/main/ui/src/main/resources/css/dark_theme.css @@ -16,6 +16,10 @@ src: url('opensans-bold.ttf'); } +@font-face { + src: url('dosis-bold.ttf'); +} + /******************************************************************************* * * * Root Styling & Colors * @@ -76,6 +80,7 @@ INDICATOR_BG: RED_5; PROGRESS_INDICATOR_BEGIN: GRAY_7; PROGRESS_INDICATOR_END: GRAY_5; + PROGRESS_BAR_BG: GRAY_2; PASSWORD_STRENGTH_INDICATOR_BG: GRAY_3; PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_0: RED_5; PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_1: ORANGE_5; @@ -460,6 +465,42 @@ -fx-padding: 4px 6px 4px 0; } +/******************************************************************************* + * * + * Text Areas * + * * + ******************************************************************************/ + +.text-area { + -fx-cursor: default; + -fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL; + -fx-background-insets: 0, 1px; + -fx-background-radius: 4px; + -fx-padding: 0; +} + +.text-input:focused { + -fx-background-color: CONTROL_BORDER_FOCUSED, CONTROL_BG_NORMAL; +} + +.text-input:disabled { + -fx-text-fill: TEXT_FILL_SECONDARY; + -fx-background-color: CONTROL_BORDER_DISABLED, CONTROL_BG_DISABLED; +} + +.text-area > .scroll-pane > .scroll-bar { + -fx-padding: 2px; +} + +.text-area .content { + -fx-padding: 0.2em 0.5em 0.2em 0.5em; + -fx-cursor: text; + -fx-text-fill: TEXT_FILL; + -fx-highlight-fill: PRIMARY_BG; + -fx-prompt-text-fill: TEXT_FILL_SECONDARY; + -fx-background-color: null; +} + /******************************************************************************* * * * Buttons * @@ -776,3 +817,31 @@ .progress-indicator:indeterminate .segment11 { -fx-shape:"m 14.321262,6.5816808 c -0.824944,0.3797564 -0.10368,1.8484772 0.718513,1.3544717 L 18.786514,5.9486042 C 19.644992,5.4932031 18.92648,4.1387308 18.068001,4.5941315 z"; } + +/******************************************************************************* + * * + * ProgressBar * + * * + ******************************************************************************/ + +.progress-bar { + -fx-indeterminate-bar-length: 100; + -fx-indeterminate-bar-escape: true; + -fx-indeterminate-bar-flip: true; + -fx-indeterminate-bar-animation-time: 2; +} + +.progress-bar > .bar { + -fx-background-color: CONTROL_PRIMARY_BG_NORMAL; + -fx-background-radius: 4px; + -fx-padding: 0.5em; +} + +.progress-bar:indeterminate > .bar { + -fx-background-color: linear-gradient(to left, transparent, CONTROL_PRIMARY_BG_NORMAL); +} + +.progress-bar > .track { + -fx-background-color: PROGRESS_BAR_BG; + -fx-background-radius: 4px; +} diff --git a/main/ui/src/main/resources/css/light_theme.css b/main/ui/src/main/resources/css/light_theme.css index 0985623db..a4ad762ed 100644 --- a/main/ui/src/main/resources/css/light_theme.css +++ b/main/ui/src/main/resources/css/light_theme.css @@ -16,6 +16,10 @@ src: url('opensans-bold.ttf'); } +@font-face { + src: url('dosis-bold.ttf'); +} + /******************************************************************************* * * * Root Styling & Colors * @@ -76,6 +80,7 @@ INDICATOR_BG: RED_5; PROGRESS_INDICATOR_BEGIN: GRAY_2; PROGRESS_INDICATOR_END: GRAY_4; + PROGRESS_BAR_BG: GRAY_8; PASSWORD_STRENGTH_INDICATOR_BG: GRAY_5; PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_0: RED_5; PASSWORD_STRENGTH_INDICATOR_BG_STRENGTH_1: ORANGE_5; @@ -460,6 +465,42 @@ -fx-padding: 4px 6px 4px 0; } +/******************************************************************************* + * * + * Text Areas * + * * + ******************************************************************************/ + +.text-area { + -fx-cursor: default; + -fx-background-color: CONTROL_BORDER_NORMAL, CONTROL_BG_NORMAL; + -fx-background-insets: 0, 1px; + -fx-background-radius: 4px; + -fx-padding: 0; +} + +.text-input:focused { + -fx-background-color: CONTROL_BORDER_FOCUSED, CONTROL_BG_NORMAL; +} + +.text-input:disabled { + -fx-text-fill: TEXT_FILL_SECONDARY; + -fx-background-color: CONTROL_BORDER_DISABLED, CONTROL_BG_DISABLED; +} + +.text-area > .scroll-pane > .scroll-bar { + -fx-padding: 2px; +} + +.text-area .content { + -fx-padding: 0.2em 0.5em 0.2em 0.5em; + -fx-cursor: text; + -fx-text-fill: TEXT_FILL; + -fx-highlight-fill: PRIMARY_BG; + -fx-prompt-text-fill: TEXT_FILL_SECONDARY; + -fx-background-color: null; +} + /******************************************************************************* * * * Buttons * @@ -776,3 +817,31 @@ .progress-indicator:indeterminate .segment11 { -fx-shape:"m 14.321262,6.5816808 c -0.824944,0.3797564 -0.10368,1.8484772 0.718513,1.3544717 L 18.786514,5.9486042 C 19.644992,5.4932031 18.92648,4.1387308 18.068001,4.5941315 z"; } + +/******************************************************************************* + * * + * ProgressBar * + * * + ******************************************************************************/ + +.progress-bar { + -fx-indeterminate-bar-length: 100; + -fx-indeterminate-bar-escape: true; + -fx-indeterminate-bar-flip: true; + -fx-indeterminate-bar-animation-time: 2; +} + +.progress-bar > .bar { + -fx-background-color: CONTROL_PRIMARY_BG_NORMAL; + -fx-background-radius: 4px; + -fx-padding: 0.5em; +} + +.progress-bar:indeterminate > .bar { + -fx-background-color: linear-gradient(to left, transparent, CONTROL_PRIMARY_BG_NORMAL); +} + +.progress-bar > .track { + -fx-background-color: PROGRESS_BAR_BG; + -fx-background-radius: 4px; +} diff --git a/main/ui/src/main/resources/fxml/addvault_existing_error.fxml b/main/ui/src/main/resources/fxml/addvault_existing_error.fxml new file mode 100644 index 000000000..c145b6283 --- /dev/null +++ b/main/ui/src/main/resources/fxml/addvault_existing_error.fxml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + +