mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-22 12:41:27 +00:00
Added unlock window (issue #928)
This commit is contained in:
@@ -10,12 +10,13 @@ import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
|
||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
import org.cryptomator.ui.unlock.UnlockComponent;
|
||||
|
||||
import javax.inject.Provider;
|
||||
import java.util.Map;
|
||||
|
||||
@Module(subcomponents = {AddVaultWizardComponent.class})
|
||||
public abstract class MainWindowModule {
|
||||
@Module(subcomponents = {AddVaultWizardComponent.class, UnlockComponent.class})
|
||||
abstract class MainWindowModule {
|
||||
|
||||
@Provides
|
||||
@MainWindow
|
||||
|
||||
@@ -2,19 +2,31 @@ package org.cryptomator.ui.mainwindow;
|
||||
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.Tasks;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.cryptomator.ui.unlock.UnlockComponent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
@MainWindowScoped
|
||||
public class VaultDetailController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(VaultDetailController.class);
|
||||
|
||||
private final ReadOnlyObjectProperty<Vault> vault;
|
||||
private final ExecutorService executor;
|
||||
private final UnlockComponent.Builder unlockWindow;
|
||||
|
||||
@Inject
|
||||
VaultDetailController(ObjectProperty<Vault> vault) {
|
||||
VaultDetailController(ObjectProperty<Vault> vault, ExecutorService executor, UnlockComponent.Builder unlockWindow) {
|
||||
this.vault = vault;
|
||||
this.executor = executor;
|
||||
this.unlockWindow = unlockWindow;
|
||||
}
|
||||
|
||||
public ReadOnlyObjectProperty<Vault> vaultProperty() {
|
||||
@@ -25,4 +37,19 @@ public class VaultDetailController implements FxController {
|
||||
return vault.get();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void unlock() {
|
||||
unlockWindow.vault(vault.get()).build().showUnlockWindow();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void lock() {
|
||||
Tasks.create(() -> {
|
||||
vault.get().lock(false);
|
||||
}).onSuccess(() -> {
|
||||
LOG.trace("Regular unmount succeeded.");
|
||||
}).onError(Exception.class, e -> {
|
||||
// TODO
|
||||
}).runOnce(executor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import javafx.application.Platform;
|
||||
import javafx.beans.Observable;
|
||||
import javafx.beans.binding.Binding;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.BooleanBinding;
|
||||
import javafx.beans.binding.StringBinding;
|
||||
import javafx.beans.property.ObjectProperty;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
@@ -62,6 +63,8 @@ public class Vault {
|
||||
private final ObjectProperty<State> state = new SimpleObjectProperty<State>(State.LOCKED);
|
||||
private final StringBinding displayableName;
|
||||
private final StringBinding displayablePath;
|
||||
private final BooleanBinding locked;
|
||||
private final BooleanBinding unlocked;
|
||||
|
||||
private Volume volume;
|
||||
|
||||
@@ -77,6 +80,8 @@ public class Vault {
|
||||
|
||||
this.displayableName = Bindings.createStringBinding(this::getDisplayableName, vaultSettings.path());
|
||||
this.displayablePath = Bindings.createStringBinding(this::getDisplayablePath, vaultSettings.path());
|
||||
this.locked = Bindings.createBooleanBinding(this::isLocked, state);
|
||||
this.unlocked = Bindings.createBooleanBinding(this::isUnlocked, state);
|
||||
}
|
||||
|
||||
// ******************************************************************************
|
||||
@@ -192,11 +197,27 @@ public class Vault {
|
||||
public State getState() {
|
||||
return state.get();
|
||||
}
|
||||
|
||||
|
||||
public BooleanBinding lockedProperty() {
|
||||
return locked;
|
||||
}
|
||||
|
||||
public boolean isLocked() {
|
||||
return state.get() == State.LOCKED;
|
||||
}
|
||||
|
||||
public BooleanBinding unlockedProperty() {
|
||||
return unlocked;
|
||||
}
|
||||
|
||||
public boolean isUnlocked() {
|
||||
return state.get() == State.UNLOCKED;
|
||||
}
|
||||
|
||||
public StringBinding displayableNameProperty() {
|
||||
return displayableName;
|
||||
}
|
||||
|
||||
|
||||
public String getDisplayableName() {
|
||||
Path p = vaultSettings.path().get();
|
||||
return p.getFileName().toString();
|
||||
|
||||
@@ -14,7 +14,7 @@ import javax.inject.Provider;
|
||||
import java.util.Map;
|
||||
|
||||
@Module
|
||||
public abstract class PreferencesModule {
|
||||
abstract class PreferencesModule {
|
||||
|
||||
@Provides
|
||||
@PreferencesWindow
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*******************************************************************************
|
||||
* 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.unlock;
|
||||
|
||||
import dagger.BindsInstance;
|
||||
import dagger.Subcomponent;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
|
||||
@UnlockScoped
|
||||
@Subcomponent(modules = {UnlockModule.class})
|
||||
public interface UnlockComponent {
|
||||
|
||||
@UnlockWindow
|
||||
Stage window();
|
||||
|
||||
@UnlockWindow
|
||||
FXMLLoaderFactory fxmlLoaders();
|
||||
|
||||
default void showUnlockWindow() {
|
||||
Stage stage = window();
|
||||
fxmlLoaders().setScene("/fxml/unlock2.fxml", stage); // TODO rename fxml file
|
||||
stage.show();
|
||||
}
|
||||
|
||||
@Subcomponent.Builder
|
||||
interface Builder {
|
||||
|
||||
@BindsInstance
|
||||
Builder vault(@UnlockWindow Vault vault);
|
||||
|
||||
UnlockComponent build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package org.cryptomator.ui.unlock;
|
||||
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.binding.ObjectBinding;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.cryptolib.api.InvalidPassphraseException;
|
||||
import org.cryptomator.cryptolib.api.UnsupportedVaultFormatException;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.Tasks;
|
||||
import org.cryptomator.ui.controls.SecPasswordField;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.nio.file.DirectoryNotEmptyException;
|
||||
import java.nio.file.NotDirectoryException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
@UnlockScoped
|
||||
public class UnlockController implements FxController {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(UnlockController.class);
|
||||
|
||||
private final Stage window;
|
||||
private final ReadOnlyObjectProperty<Vault> vault;
|
||||
private final ExecutorService executor;
|
||||
private final ObjectBinding<ContentDisplay> unlockButtonState;
|
||||
public SecPasswordField passwordField;
|
||||
|
||||
@Inject
|
||||
public UnlockController(@UnlockWindow Stage window, @UnlockWindow ReadOnlyObjectProperty<Vault> vault, ExecutorService executor) {
|
||||
this.window = window;
|
||||
this.vault = vault;
|
||||
this.executor = executor;
|
||||
this.unlockButtonState = Bindings.createObjectBinding(this::getUnlockButtonState, vault.get().stateProperty());
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void cancel() {
|
||||
LOG.debug("Unlock canceled by user.");
|
||||
window.close();
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void unlock() {
|
||||
CharSequence password = passwordField.getCharacters();
|
||||
Tasks.create(() -> {
|
||||
vault.get().unlock(password);
|
||||
// if (keychainAccess.isPresent() && savePassword.isSelected()) {
|
||||
// keychainAccess.get().storePassphrase(vault.getId(), password);
|
||||
// }
|
||||
}).onSuccess(() -> {
|
||||
passwordField.swipe();
|
||||
LOG.info("Unlock of '{}' succeeded.", vault.get().getDisplayableName());
|
||||
window.close();
|
||||
}).onError(InvalidPassphraseException.class, e -> {
|
||||
passwordField.selectAll();
|
||||
passwordField.requestFocus();
|
||||
}).onError(UnsupportedVaultFormatException.class, e -> {
|
||||
// TODO
|
||||
}).onError(NotDirectoryException.class, e -> {
|
||||
LOG.error("Unlock failed. Mount point not a directory: {}", e.getMessage());
|
||||
// TODO
|
||||
}).onError(DirectoryNotEmptyException.class, e -> {
|
||||
LOG.error("Unlock failed. Mount point not empty: {}", e.getMessage());
|
||||
// TODO
|
||||
}).onError(Exception.class, e -> { // including RuntimeExceptions
|
||||
LOG.error("Unlock failed for technical reasons.", e);
|
||||
// TODO
|
||||
}).runOnce(executor);
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public ReadOnlyObjectProperty<Vault> vaultProperty() {
|
||||
return vault;
|
||||
}
|
||||
|
||||
public Vault getVault() {
|
||||
return vault.get();
|
||||
}
|
||||
|
||||
public ObjectBinding<ContentDisplay> unlockButtonStateProperty() {
|
||||
return unlockButtonState;
|
||||
}
|
||||
|
||||
public ContentDisplay getUnlockButtonState() {
|
||||
switch (vault.get().getState()) {
|
||||
case PROCESSING:
|
||||
return ContentDisplay.LEFT;
|
||||
default:
|
||||
return ContentDisplay.TEXT_ONLY;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package org.cryptomator.ui.unlock;
|
||||
|
||||
import dagger.Binds;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoMap;
|
||||
import javafx.beans.property.ReadOnlyObjectProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
import org.cryptomator.ui.common.FXMLLoaderFactory;
|
||||
import org.cryptomator.ui.common.FxController;
|
||||
import org.cryptomator.ui.common.FxControllerKey;
|
||||
import org.cryptomator.ui.model.Vault;
|
||||
|
||||
import javax.inject.Provider;
|
||||
import java.util.Map;
|
||||
|
||||
@Module
|
||||
abstract class UnlockModule {
|
||||
|
||||
@Provides
|
||||
@UnlockWindow
|
||||
@UnlockScoped
|
||||
static FXMLLoaderFactory provideFxmlLoaderFactory(Map<Class<? extends FxController>, Provider<FxController>> factories) {
|
||||
return new FXMLLoaderFactory(factories);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@UnlockWindow
|
||||
@UnlockScoped
|
||||
static Stage provideStage() {
|
||||
Stage stage = new Stage();
|
||||
stage.setMinWidth(300);
|
||||
stage.setMinHeight(200);
|
||||
stage.initModality(Modality.APPLICATION_MODAL);
|
||||
return stage;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@UnlockWindow
|
||||
@UnlockScoped
|
||||
static ReadOnlyObjectProperty<Vault> provideVaultProperty(@UnlockWindow Vault vault) {
|
||||
return new SimpleObjectProperty<>(vault);
|
||||
}
|
||||
|
||||
// ------------------
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@FxControllerKey(UnlockController.class)
|
||||
abstract FxController bindUnlockController(UnlockController controller);
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.cryptomator.ui.unlock;
|
||||
|
||||
import javax.inject.Scope;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@Scope
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface UnlockScoped {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.cryptomator.ui.unlock;
|
||||
|
||||
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)
|
||||
public @interface UnlockWindow {
|
||||
|
||||
}
|
||||
@@ -265,3 +265,58 @@
|
||||
.menu-item:focused {
|
||||
-fx-background-color: CONTROL_BG_ARMED;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* *
|
||||
* ProgressIndicator *
|
||||
* Derived from aquafx-project.com, (C) Claudine Zillmann, see NOTICE.md *
|
||||
* *
|
||||
****************************************************************************/
|
||||
|
||||
.progress-indicator {
|
||||
-fx-indeterminate-segment-count: 12;
|
||||
-fx-spin-enabled: true;
|
||||
}
|
||||
.progress-indicator:indeterminate > .spinner {
|
||||
-fx-padding: 0.083333em;
|
||||
}
|
||||
.progress-indicator:indeterminate .segment {
|
||||
-fx-background-color: rgb(95.0, 95.0, 98.0), rgb(122.0, 122.0, 125.0);
|
||||
-fx-background-insets:0.0, 0.5;
|
||||
}
|
||||
.progress-indicator:indeterminate .segment0 {
|
||||
-fx-shape:"m 12.007729,4.9541827 c -0.49762,0.7596865 0.893181,1.6216808 1.327833,0.7666252 L 15.456199,2.0477574 C 15.942094,1.2061627 14.61426,0.43953765 14.128365,1.2811324 z";
|
||||
}
|
||||
.progress-indicator:indeterminate .segment1 {
|
||||
-fx-shape:"m 9.2224559,4.62535 c -0.051108,0.9067177 1.5843581,0.957826 1.5332501,0 l 0,-4.24127319 c 0,-0.9717899 -1.5332501,-0.9717899 -1.5332501,0 z";
|
||||
}
|
||||
.progress-indicator:indeterminate .segment2 {
|
||||
-fx-shape:"M 8.0465401,4.9030617 C 8.5441601,5.6627485 7.1533584,6.5247425 6.7187068,5.6696872 L 4.5980702,1.9966363 C 4.1121752,1.1550418 5.4400085,0.38841683 5.9259035,1.2300114 z";
|
||||
}
|
||||
.progress-indicator:indeterminate .segment3 {
|
||||
-fx-shape:"M 5.7330066,6.5305598 C 6.5579512,6.9103162 5.8366865,8.3790371 5.0144939,7.8850315 L 1.2677551,5.8974832 C 0.409277,5.4420823 1.1277888,4.0876101 1.9862674,4.5430105 z";
|
||||
}
|
||||
.progress-indicator:indeterminate .segment4 {
|
||||
-fx-shape:"m 0.42171041,9.2083842 c -0.90671825,-0.051108 -0.95782608,1.5843588 0,1.5332498 l 4.24127319,0 c 0.9717899,0 0.9717899,-1.5332498 0,-1.5332498 z";
|
||||
}
|
||||
.progress-indicator:indeterminate .segment5 {
|
||||
-fx-shape:"M 5.7330066,13.443113 C 6.5579512,13.063356 5.8366865,11.594635 5.0144939,12.088641 L 1.2677551,14.076189 C 0.409277,14.53159 1.1277888,15.886062 1.9862674,15.430662 z";
|
||||
}
|
||||
.progress-indicator:indeterminate .segment6 {
|
||||
-fx-shape:"M 8.0465401,15.070611 C 8.5441601,14.310924 7.1533584,13.44893 6.7187068,14.303985 l -2.1206366,3.673051 c -0.485895,0.841595 0.8419383,1.60822 1.3278333,0.766625 z";
|
||||
}
|
||||
.progress-indicator:indeterminate .segment7 {
|
||||
-fx-shape:"m 9.2224559,19.539943 c -0.051108,0.906718 1.5843581,0.957826 1.5332501,0 l 0,-4.241273 c 0,-0.97179 -1.5332501,-0.97179 -1.5332501,0 z";
|
||||
}
|
||||
.progress-indicator:indeterminate .segment8 {
|
||||
-fx-shape:"m 12.10997,15.070611 c -0.49762,-0.759687 0.893182,-1.621681 1.327834,-0.766626 l 2.120636,3.673051 c 0.485895,0.841595 -0.841938,1.60822 -1.327833,0.766625 z";
|
||||
}
|
||||
.progress-indicator:indeterminate .segment9 {
|
||||
-fx-shape:"m 14.423504,13.443113 c -0.824945,-0.379757 -0.10368,-1.848478 0.718512,-1.354472 l 3.746739,1.987548 c 0.858478,0.455401 0.139967,1.809873 -0.718512,1.354473 z";
|
||||
}
|
||||
.progress-indicator:indeterminate .segment10 {
|
||||
-fx-shape:"m 15.372451,9.2445322 c -0.906719,-0.051108 -0.957826,1.5843588 0,1.5332498 l 4.241273,0 c 0.97179,0 0.97179,-1.5332498 0,-1.5332498 z";
|
||||
}
|
||||
.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";
|
||||
}
|
||||
36
main/ui/src/main/resources/fxml/unlock2.fxml
Normal file
36
main/ui/src/main/resources/fxml/unlock2.fxml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.Region?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.text.Text?>
|
||||
<?import javafx.scene.text.TextFlow?>
|
||||
<?import org.cryptomator.ui.controls.SecPasswordField?>
|
||||
<?import javafx.scene.control.ProgressIndicator?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.cryptomator.ui.unlock.UnlockController"
|
||||
minWidth="300"
|
||||
spacing="6">
|
||||
<padding>
|
||||
<Insets bottom="6.0" left="6.0" right="6.0" top="6.0"/>
|
||||
</padding>
|
||||
<children>
|
||||
<TextFlow>
|
||||
<Text text="TODO Enter Password for "/>
|
||||
<Text text="${controller.vault.displayableName}"/>
|
||||
</TextFlow>
|
||||
<SecPasswordField fx:id="passwordField"/>
|
||||
<HBox>
|
||||
<Button text="TODO cancel" cancelButton="true" onAction="#cancel"/>
|
||||
<Region HBox.hgrow="ALWAYS"/>
|
||||
<Button text="TODO unlock" defaultButton="true" onAction="#unlock" contentDisplay="${controller.unlockButtonState}" disable="${passwordField.text.empty}">
|
||||
<graphic>
|
||||
<ProgressIndicator progress="-1" prefWidth="12" prefHeight="12"/>
|
||||
</graphic>
|
||||
</Button>
|
||||
</HBox>
|
||||
</children>
|
||||
</VBox>
|
||||
@@ -3,6 +3,7 @@
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<VBox xmlns="http://javafx.com/javafx"
|
||||
xmlns:fx="http://javafx.com/fxml"
|
||||
fx:controller="org.cryptomator.ui.mainwindow.VaultDetailController"
|
||||
@@ -11,6 +12,9 @@
|
||||
<Insets bottom="6.0" left="6.0" right="6.0" top="6.0"/>
|
||||
</padding>
|
||||
<children>
|
||||
<Label text="${controller.vault}"/>
|
||||
<Label text="${controller.vault.displayableName}"/>
|
||||
|
||||
<Button text="TODO unlock" onAction="#unlock" visible="${controller.vault.locked}"/>
|
||||
<Button text="TODO lock" onAction="#lock" visible="${controller.vault.unlocked}"/>
|
||||
</children>
|
||||
</VBox>
|
||||
|
||||
Reference in New Issue
Block a user