mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-21 20:21:27 +00:00
Merge branch 'develop' into feature/admin-properties
This commit is contained in:
@@ -4,24 +4,27 @@ import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.AccessibleRole;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ContentDisplay;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.layout.HBox;
|
||||
import javafx.scene.layout.Region;
|
||||
import javafx.scene.layout.VBox;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
public class NotificationBar extends HBox {
|
||||
public class InfoBar extends HBox {
|
||||
|
||||
@FXML
|
||||
private Label notificationLabel;
|
||||
private Label infoMessage;
|
||||
|
||||
private final BooleanProperty dismissable = new SimpleBooleanProperty();
|
||||
private final BooleanProperty notify = new SimpleBooleanProperty();
|
||||
|
||||
|
||||
public NotificationBar() {
|
||||
public InfoBar() {
|
||||
setAlignment(Pos.CENTER);
|
||||
setStyle("-fx-alignment: center;");
|
||||
getStyleClass().addAll("info-bar");
|
||||
|
||||
Region spacer = new Region();
|
||||
spacer.setMinWidth(40);
|
||||
@@ -36,14 +39,21 @@ public class NotificationBar extends HBox {
|
||||
vbox.setAlignment(Pos.CENTER);
|
||||
HBox.setHgrow(vbox, javafx.scene.layout.Priority.ALWAYS);
|
||||
|
||||
notificationLabel = new Label();
|
||||
notificationLabel.getStyleClass().add("notification-label");
|
||||
notificationLabel.setStyle("-fx-alignment: center;");
|
||||
vbox.getChildren().add(notificationLabel);
|
||||
infoMessage = new Label();
|
||||
infoMessage.setFocusTraversable(true);
|
||||
infoMessage.setAccessibleRole(AccessibleRole.BUTTON);
|
||||
vbox.getChildren().add(infoMessage);
|
||||
|
||||
Button closeButton = new Button("X");
|
||||
var closeGraphic = new FontAwesome5IconView();
|
||||
closeGraphic.setGlyph(FontAwesome5Icon.TIMES);
|
||||
closeGraphic.setGlyphSize(12);
|
||||
closeGraphic.getStyleClass().add("glyph");
|
||||
|
||||
Button closeButton = new Button();
|
||||
closeButton.setGraphic(closeGraphic);
|
||||
closeButton.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
|
||||
closeButton.setAccessibleText(ResourceBundle.getBundle("i18n.strings").getString("main.notification.closeButton.tooltip"));
|
||||
closeButton.setMinWidth(40);
|
||||
closeButton.setStyle("-fx-background-color: transparent; -fx-text-fill: white; -fx-font-weight: bold;");
|
||||
closeButton.visibleProperty().bind(dismissable);
|
||||
|
||||
closeButton.setOnAction(_ -> {
|
||||
@@ -61,11 +71,11 @@ public class NotificationBar extends HBox {
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return notificationLabel.getText();
|
||||
return infoMessage.getText();
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
notificationLabel.setText(text);
|
||||
infoMessage.setText(text);
|
||||
}
|
||||
|
||||
public void setStyleClass(String styleClass) {
|
||||
@@ -16,6 +16,7 @@ import javafx.beans.value.ObservableValue;
|
||||
import javafx.fxml.FXML;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.layout.HBox;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
// unscoped because each cell needs its own controller
|
||||
public class VaultListCellController implements FxController {
|
||||
@@ -23,9 +24,12 @@ public class VaultListCellController implements FxController {
|
||||
private static final Insets COMPACT_INSETS = new Insets(6, 12, 6, 12);
|
||||
private static final Insets DEFAULT_INSETS = new Insets(12);
|
||||
|
||||
private final ResourceBundle resourceBundle;
|
||||
private final ObjectProperty<Vault> vault = new SimpleObjectProperty<>();
|
||||
private final ObservableValue<VaultState.Value> vaultState;
|
||||
private final ObservableValue<FontAwesome5Icon> glyph;
|
||||
private final ObservableValue<Boolean> compactMode;
|
||||
private final ObservableValue<String> accessibleText;
|
||||
|
||||
private AutoAnimator spinAnimation;
|
||||
|
||||
@@ -35,17 +39,21 @@ public class VaultListCellController implements FxController {
|
||||
public HBox vaultListCell;
|
||||
|
||||
@Inject
|
||||
VaultListCellController(Settings settings) {
|
||||
this.glyph = vault.flatMap(Vault::stateProperty).map(this::getGlyphForVaultState);
|
||||
VaultListCellController(Settings settings, ResourceBundle resourceBundle) {
|
||||
this.resourceBundle = resourceBundle;
|
||||
this.vaultState = vault.flatMap(Vault::stateProperty);
|
||||
this.glyph = vaultState.map(this::getGlyphForVaultState);
|
||||
this.accessibleText = vaultState.map(this::getAccessibleTextForVaultState);
|
||||
this.compactMode = settings.compactMode;
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
this.spinAnimation = AutoAnimator.animate(Animations.createDiscrete360Rotation(vaultStateView)) //
|
||||
.onCondition(vault.flatMap(Vault::stateProperty).map(VaultState.Value.PROCESSING::equals).orElse(false)) //
|
||||
.onCondition(vaultState.map(VaultState.Value.PROCESSING::equals).orElse(false)) //
|
||||
.afterStop(() -> vaultStateView.setRotate(0)) //
|
||||
.build();
|
||||
this.vaultListCell.paddingProperty().bind(compactMode.map(c -> c ? COMPACT_INSETS : DEFAULT_INSETS));
|
||||
this.vaultListCell.accessibleTextProperty().bind(accessibleText);
|
||||
}
|
||||
|
||||
// TODO deduplicate w/ VaultDetailController
|
||||
@@ -62,6 +70,25 @@ public class VaultListCellController implements FxController {
|
||||
}
|
||||
}
|
||||
|
||||
private String getAccessibleTextForVaultState(VaultState.Value state) {
|
||||
var v = vault.get();
|
||||
if (state != null && v != null) {
|
||||
var translationKey = switch (state) {
|
||||
case LOCKED -> "vault.state.locked";
|
||||
case PROCESSING -> "vault.state.processing";
|
||||
case UNLOCKED -> "vault.state.unlocked";
|
||||
case NEEDS_MIGRATION -> "vault.state.migrationNeeded";
|
||||
case MISSING -> "vault.state.missing";
|
||||
case VAULT_CONFIG_MISSING, ALL_MISSING, ERROR -> "vault.state.error";
|
||||
};
|
||||
|
||||
var localizedState = resourceBundle.getString(translationKey);
|
||||
return resourceBundle.getString("main.vaultlist.listEntry").formatted(v.getDisplayName(), localizedState);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/* Getter/Setter */
|
||||
|
||||
public ObservableValue<FontAwesome5Icon> glyphProperty() {
|
||||
|
||||
@@ -104,7 +104,7 @@ public class RecoveryKeyCreationController implements FxController {
|
||||
descriptionLabel.formatProperty().set(resourceBundle.getString("recoveryKey.recover.description"));
|
||||
cancelButton.setOnAction((_) -> back());
|
||||
cancelButton.setText(resourceBundle.getString("generic.button.back"));
|
||||
nextButton.setOnAction((_) -> restoreWithPassword());
|
||||
nextButton.setOnAction((_) -> restoreWithPasswordAsync());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,11 +137,47 @@ public class RecoveryKeyCreationController implements FxController {
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void restoreWithPassword() {
|
||||
public void restoreWithPasswordAsync() {
|
||||
Task<Void> task = RecoveryKeyTasks.createTask(this::restoreWithPassword);
|
||||
|
||||
task.setOnScheduled(_ -> {
|
||||
LOG.debug("Restoring vault configuration with password for {}.", vault.getDisplayablePath());
|
||||
});
|
||||
|
||||
task.setOnSucceeded(_ -> {
|
||||
LOG.debug("Restored vault configuration for {}.", vault.getDisplayablePath());
|
||||
try {
|
||||
if (!vaultListManager.isAlreadyAdded(vault.getPath())) {
|
||||
vaultListManager.add(vault.getPath());
|
||||
}
|
||||
window.close();
|
||||
dialogs.prepareRecoverPasswordSuccess((Stage) window.getOwner()) //
|
||||
.setTitleKey("recover.recoverVaultConfig.title") //
|
||||
.setMessageKey("recoveryKey.recover.resetVaultConfigSuccess.message") //
|
||||
.setDescriptionKey("recoveryKey.recover.resetMasterkeyFileSuccess.description")
|
||||
.build().showAndWait();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to add vault to list.", e);
|
||||
appWindows.showErrorWindow(e, window, null);
|
||||
}
|
||||
});
|
||||
|
||||
task.setOnFailed(_ -> {
|
||||
if (task.getException() instanceof InvalidPassphraseException e) {
|
||||
LOG.info("Password invalid", e);
|
||||
Animations.createShakeWindowAnimation(window).play();
|
||||
} else {
|
||||
LOG.error("Recovery process failed.", task.getException());
|
||||
appWindows.showErrorWindow(task.getException(), window, null);
|
||||
}
|
||||
});
|
||||
|
||||
executor.submit(task);
|
||||
}
|
||||
|
||||
void restoreWithPassword() throws IOException, CryptoException {
|
||||
try (RecoveryDirectory recoveryDirectory = RecoveryDirectory.create(vault.getPath())) {
|
||||
Path recoveryPath = recoveryDirectory.getRecoveryPath();
|
||||
|
||||
Path masterkeyFilePath = vault.getPath().resolve(MASTERKEY_FILENAME);
|
||||
|
||||
try (Masterkey masterkey = MasterkeyService.load(masterkeyFileAccess, masterkeyFilePath, passwordField.getCharacters())) {
|
||||
@@ -152,23 +188,6 @@ public class RecoveryKeyCreationController implements FxController {
|
||||
}
|
||||
|
||||
recoveryDirectory.moveRecoveredFile(VAULTCONFIG_FILENAME);
|
||||
|
||||
if (!vaultListManager.isAlreadyAdded(vault.getPath())) {
|
||||
vaultListManager.add(vault.getPath());
|
||||
}
|
||||
window.close();
|
||||
dialogs.prepareRecoverPasswordSuccess((Stage)window.getOwner()) //
|
||||
.setTitleKey("recover.recoverVaultConfig.title") //
|
||||
.setMessageKey("recoveryKey.recover.resetVaultConfigSuccess.message") //
|
||||
.setDescriptionKey("recoveryKey.recover.resetMasterkeyFileSuccess.description")
|
||||
.build().showAndWait();
|
||||
|
||||
} catch (InvalidPassphraseException e) {
|
||||
LOG.info("Password invalid", e);
|
||||
Animations.createShakeWindowAnimation(window).play();
|
||||
} catch (IOException | CryptoException | IllegalStateException e) {
|
||||
LOG.error("Recovery process failed", e);
|
||||
appWindows.showErrorWindow(e, window, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -117,14 +117,46 @@ public class RecoveryKeyResetPasswordController implements FxController {
|
||||
@FXML
|
||||
public void next() {
|
||||
switch (recoverType.get()) {
|
||||
case RESTORE_ALL -> restorePassword();
|
||||
case RESTORE_ALL -> restorePasswordAsync();
|
||||
case RESTORE_MASTERKEY, RESET_PASSWORD -> resetPassword();
|
||||
default -> resetPassword(); // Fallback
|
||||
}
|
||||
}
|
||||
|
||||
@FXML
|
||||
public void restorePassword() {
|
||||
public void restorePasswordAsync() {
|
||||
Task<Void> task = RecoveryKeyTasks.createTask(this::restorePassword);
|
||||
|
||||
task.setOnScheduled(_ -> {
|
||||
LOG.debug("Restoring vault configuration for {}.", vault.getDisplayablePath());
|
||||
});
|
||||
|
||||
task.setOnSucceeded(_ -> {
|
||||
LOG.debug("Restored vault configuration for {}.", vault.getDisplayablePath());
|
||||
try {
|
||||
if (!vaultListManager.isAlreadyAdded(vault.getPath())) {
|
||||
vaultListManager.add(vault.getPath());
|
||||
}
|
||||
window.close();
|
||||
dialogs.prepareRecoverPasswordSuccess((Stage) window.getOwner()) //
|
||||
.setTitleKey("recover.recoverVaultConfig.title") //
|
||||
.setMessageKey("recoveryKey.recover.resetVaultConfigSuccess.message") //
|
||||
.build().showAndWait();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to add vault to list.", e);
|
||||
appWindows.showErrorWindow(e, window, null);
|
||||
}
|
||||
});
|
||||
|
||||
task.setOnFailed(_ -> {
|
||||
LOG.error("Recovery process failed.", task.getException());
|
||||
appWindows.showErrorWindow(task.getException(), window, null);
|
||||
});
|
||||
|
||||
executor.submit(task);
|
||||
}
|
||||
|
||||
void restorePassword() throws IOException, CryptoException {
|
||||
try (RecoveryDirectory recoveryDirectory = RecoveryDirectory.create(vault.getPath())) {
|
||||
Path recoveryPath = recoveryDirectory.getRecoveryPath();
|
||||
MasterkeyService.recoverFromRecoveryKey(recoveryKey.get(), recoveryKeyFactory, recoveryPath, newPasswordController.passwordField.getCharacters());
|
||||
@@ -135,19 +167,6 @@ public class RecoveryKeyResetPasswordController implements FxController {
|
||||
|
||||
recoveryDirectory.moveRecoveredFile(MASTERKEY_FILENAME);
|
||||
recoveryDirectory.moveRecoveredFile(VAULTCONFIG_FILENAME);
|
||||
|
||||
if (!vaultListManager.isAlreadyAdded(vault.getPath())) {
|
||||
vaultListManager.add(vault.getPath());
|
||||
}
|
||||
window.close();
|
||||
dialogs.prepareRecoverPasswordSuccess((Stage)window.getOwner()) //
|
||||
.setTitleKey("recover.recoverVaultConfig.title") //
|
||||
.setMessageKey("recoveryKey.recover.resetVaultConfigSuccess.message") //
|
||||
.build().showAndWait();
|
||||
|
||||
} catch (IOException | CryptoException e) {
|
||||
LOG.error("Recovery process failed", e);
|
||||
appWindows.showErrorWindow(e, window, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.cryptomator.ui.recoverykey;
|
||||
|
||||
import javafx.concurrent.Task;
|
||||
|
||||
final class RecoveryKeyTasks {
|
||||
|
||||
private RecoveryKeyTasks() {
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface TaskAction {
|
||||
void run() throws Exception;
|
||||
}
|
||||
|
||||
static Task<Void> createTask(TaskAction action) {
|
||||
return new Task<Void>() {
|
||||
@Override
|
||||
protected Void call() throws Exception {
|
||||
action.run();
|
||||
return null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user