Переглянути джерело

Merge branch 'develop' into feature/touch-id

Armin Schrenk 2 тижнів тому
батько
коміт
c5c3d55658

+ 9 - 9
.github/ISSUE_TEMPLATE/bug.yml

@@ -1,7 +1,14 @@
 name: Bug Report
 description: Create a report to help us improve
-labels: ["type:bug"]
+type: "Bug"
 body:
+  - type: input
+    id: summary
+    attributes:
+      label: Summary
+      placeholder: Please summarize your problem.
+    validations:
+      required: true
   - type: checkboxes
     id: terms
     attributes:
@@ -11,13 +18,6 @@ body:
         required: true
       - label: I agree to follow this project's [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md)
         required: true
-  - type: input
-    id: summary
-    attributes:
-      label: Summary
-      placeholder: Please summarize your problem.
-    validations:
-      required: true
   - type: textarea
     id: software-versions
     attributes:
@@ -97,4 +97,4 @@ body:
     id: further-info
     attributes:
       label: Anything else?
-      description: Links? References? Screenshots? Configurations? Any data that might be necessary to reproduce the issue?
+      description: Links? References? Screenshots? Configurations? Any data that might be necessary to reproduce the issue?

+ 8 - 8
.github/ISSUE_TEMPLATE/feature.yml

@@ -1,7 +1,14 @@
 name: Feature Request
 description: Suggest an idea for this project
-labels: ["type:feature-request"]
+type: "Feature"
 body:
+  - type: input
+    id: summary
+    attributes:
+      label: Summary
+      placeholder: Please summarize your feature request.
+    validations:
+      required: true
   - type: checkboxes
     id: terms
     attributes:
@@ -11,13 +18,6 @@ body:
         required: true
       - label: I agree to follow this project's [Code of Conduct](https://github.com/cryptomator/cryptomator/blob/develop/.github/CODE_OF_CONDUCT.md)
         required: true
-  - type: input
-    id: summary
-    attributes:
-      label: Summary
-      placeholder: Please summarize your feature request.
-    validations:
-      required: true
   - type: textarea
     id: motivation
     attributes:

+ 1 - 1
.github/workflows/debian.yml

@@ -28,7 +28,7 @@ env:
 jobs:
   build:
     name: Build Debian Package
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-22.04
     steps:
       - uses: actions/checkout@v4
       - id: versions

+ 3 - 1
src/main/java/org/cryptomator/ui/addvaultwizard/ReadmeGenerator.java

@@ -76,8 +76,10 @@ public class ReadmeGenerator {
 		input.chars().forEachOrdered(c -> {
 			if (c < 128) {
 				sb.append((char) c);
+			} else if (c <= 0xFF) {
+				sb.append("\\'").append(String.format("%02X", c));
 			} else if (c < 0xFFFF) {
-				sb.append("\\u").append(c);
+				sb.append("\\uc1\\u").append(c);
 			}
 		});
 	}

+ 2 - 2
src/main/java/org/cryptomator/ui/dialogs/Dialogs.java

@@ -38,7 +38,7 @@ public class Dialogs {
 				.setMessageKey("removeVault.message") //
 				.setDescriptionKey("removeVault.description") //
 				.setIcon(FontAwesome5Icon.QUESTION) //
-				.setOkButtonKey("removeVault.confirmBtn") //
+				.setOkButtonKey("generic.button.remove") //
 				.setCancelButtonKey("generic.button.cancel") //
 				.setOkAction(stage -> {
 					LOG.debug("Removing vault {}.", vault.getDisplayName());
@@ -54,7 +54,7 @@ public class Dialogs {
 				.setMessageKey("removeCert.message") //
 				.setDescriptionKey("removeCert.description") //
 				.setIcon(FontAwesome5Icon.QUESTION) //
-				.setOkButtonKey("removeCert.confirmBtn") //
+				.setOkButtonKey("generic.button.remove") //
 				.setCancelButtonKey("generic.button.cancel") //
 				.setOkAction(stage -> {
 					settings.licenseKey.set(null);

+ 3 - 4
src/main/java/org/cryptomator/ui/dialogs/SimpleDialog.java

@@ -31,8 +31,9 @@ public class SimpleDialog {
 		FxmlLoaderFactory loaderFactory = FxmlLoaderFactory.forController( //
 				new SimpleDialogController(resolveText(builder.messageKey, null), //
 						resolveText(builder.descriptionKey, null), //
-						builder.icon, resolveText(builder.okButtonKey, null), //
-						resolveText(builder.cancelButtonKey, null), //
+						builder.icon, //
+						resolveText(builder.okButtonKey, null), //
+						builder.cancelButtonKey != null ? resolveText(builder.cancelButtonKey, null) : null, //
 						() -> builder.okAction.accept(dialogStage), //
 						() -> builder.cancelAction.accept(dialogStage)), //
 				Scene::new, builder.resourceBundle);
@@ -67,7 +68,6 @@ public class SimpleDialog {
 		private String descriptionKey;
 		private String okButtonKey;
 		private String cancelButtonKey;
-
 		private FontAwesome5Icon icon;
 		private Consumer<Stage> okAction = Stage::close;
 		private Consumer<Stage> cancelAction = Stage::close;
@@ -128,7 +128,6 @@ public class SimpleDialog {
 			Objects.requireNonNull(messageKey, "SimpleDialog messageKey must be set.");
 			Objects.requireNonNull(descriptionKey, "SimpleDialog descriptionKey must be set.");
 			Objects.requireNonNull(okButtonKey, "SimpleDialog okButtonKey must be set.");
-			Objects.requireNonNull(cancelButtonKey, "SimpleDialog cancelButtonKey must be set.");
 
 			try {
 				return new SimpleDialog(this);

+ 6 - 0
src/main/java/org/cryptomator/ui/dialogs/SimpleDialogController.java

@@ -14,6 +14,7 @@ public class SimpleDialogController implements FxController {
 	private final String cancelButtonText;
 	private final Runnable okAction;
 	private final Runnable cancelAction;
+	private final boolean cancelButtonVisible;
 
 	public SimpleDialogController(String message, String description, FontAwesome5Icon icon, String okButtonText, String cancelButtonText, Runnable okAction, Runnable cancelAction) {
 		this.message = message;
@@ -23,6 +24,11 @@ public class SimpleDialogController implements FxController {
 		this.cancelButtonText = cancelButtonText;
 		this.okAction = okAction;
 		this.cancelAction = cancelAction;
+		this.cancelButtonVisible = cancelButtonText != null && !cancelButtonText.isEmpty();
+	}
+
+	public boolean isCancelButtonVisible() {
+		return cancelButtonVisible;
 	}
 
 	public String getMessage() {

+ 1 - 1
src/main/java/org/cryptomator/ui/keyloading/hub/ReceiveKeyController.java

@@ -165,7 +165,7 @@ public class ReceiveKeyController implements FxController {
 		var vaultKeyUri = hubConfig.URIs.API.resolve("vaults/" + vaultId + "/access-token");
 		var request = HttpRequest.newBuilder(vaultKeyUri) //
 				.header("Authorization", "Bearer " + bearerToken) //
-				.header("deviceId", deviceId) //
+				.header("Hub-Device-ID", deviceId) //
 				.GET() //
 				.timeout(REQ_TIMEOUT) //
 				.build();

+ 42 - 10
src/main/java/org/cryptomator/ui/mainwindow/MainWindowController.java

@@ -19,9 +19,11 @@ import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.ReadOnlyBooleanProperty;
 import javafx.beans.property.ReadOnlyObjectProperty;
 import javafx.fxml.FXML;
+import javafx.geometry.Rectangle2D;
 import javafx.scene.layout.StackPane;
 import javafx.stage.Screen;
 import javafx.stage.Stage;
+import javafx.stage.WindowEvent;
 
 @MainWindowScoped
 public class MainWindowController implements FxController {
@@ -68,18 +70,15 @@ public class MainWindowController implements FxController {
 		int y = settings.windowYPosition.get();
 		int width = settings.windowWidth.get();
 		int height = settings.windowHeight.get();
-		if (windowPositionSaved(x, y, width, height) ) {
-			if(isWithinDisplayBounds(x, y, width, height)) { //use stored window position
-				window.setX(x);
-				window.setY(y);
-				window.setWidth(Math.clamp(width, window.getMinWidth(), window.getMaxWidth()));
-				window.setHeight(Math.clamp(height, window.getMinHeight(), window.getMaxHeight()));
-			} else if(isWithinDisplayBounds((int) window.getX(), (int) window.getY(), width, height)) { //just reset position of upper left corner, keep window size
-				window.setWidth(Math.clamp(width, window.getMinWidth(), window.getMaxWidth()));
-				window.setHeight(Math.clamp(height, window.getMinHeight(), window.getMaxHeight()));
-			} //else reset window completely
+		if (windowPositionSaved(x, y, width, height)) {
+			window.setX(x);
+			window.setY(y);
+			window.setWidth(Math.clamp(width, window.getMinWidth(), window.getMaxWidth()));
+			window.setHeight(Math.clamp(height, window.getMinHeight(), window.getMaxHeight()));
 		}
 
+		window.setOnShowing(this::checkDisplayBounds);
+
 		settings.windowXPosition.bind(window.xProperty());
 		settings.windowYPosition.bind(window.yProperty());
 		settings.windowWidth.bind(window.widthProperty());
@@ -90,6 +89,39 @@ public class MainWindowController implements FxController {
 		return x != 0 || y != 0 || width != 0 || height != 0;
 	}
 
+	private void checkDisplayBounds(WindowEvent windowEvent) {
+		int x = settings.windowXPosition.get();
+		int y = settings.windowYPosition.get();
+		int width = settings.windowWidth.get();
+		int height = settings.windowHeight.get();
+
+		// Minimizing a window in Windows and closing it could result in an out of bounds position at (x, y) = (-32000, -32000)
+		// See https://devblogs.microsoft.com/oldnewthing/20041028-00/?p=37453
+		// If the position is (-32000, -32000), restore to the last saved position
+		if (window.getX() == -32000 && window.getY() == -32000) {
+			window.setX(x);
+			window.setY(y);
+			window.setWidth(width);
+			window.setHeight(height);
+		}
+
+		Rectangle2D primaryScreenBounds = Screen.getPrimary().getBounds();
+		if (!isWithinDisplayBounds(x, y, width, height)) { //use stored window position
+			LOG.debug("Resetting window position due to insufficient screen overlap");
+			var centeredX = (primaryScreenBounds.getWidth() - window.getMinWidth()) / 2;
+			var centeredY = (primaryScreenBounds.getHeight() - window.getMinHeight()) / 2;
+			//check if we can keep width and height
+			if (isWithinDisplayBounds((int) centeredX, (int) centeredY, width, height)) {
+				//if so, keep window size
+				window.setWidth(Math.clamp(width, window.getMinWidth(), window.getMaxWidth()));
+				window.setHeight(Math.clamp(height, window.getMinHeight(), window.getMaxHeight()));
+			}
+			//reset position of upper left corner
+			window.setX(centeredX);
+			window.setY(centeredY);
+		}
+	}
+
 	private boolean isWithinDisplayBounds(int x, int y, int width, int height) {
 		// define a rect which is inset on all sides from the window's rect:
 		final int shrinkedX = x + 20; // 20px left

+ 6 - 0
src/main/resources/css/dark_theme.css

@@ -359,6 +359,12 @@
 	-fx-background-color: PRIMARY;
 }
 
+.notification-debug:hover .notification-label,
+.notification-update:hover .notification-label,
+.notification-support:hover .notification-label {
+ 	-fx-underline:true;
+}
+
 /*******************************************************************************
  *                                                                             *
  * ScrollBar                                                                   *

+ 6 - 0
src/main/resources/css/light_theme.css

@@ -358,6 +358,12 @@
 	-fx-background-color: PRIMARY;
 }
 
+.notification-debug:hover .notification-label,
+.notification-update:hover .notification-label,
+.notification-support:hover .notification-label {
+	-fx-underline:true;
+}
+
 /*******************************************************************************
  *                                                                             *
  * ScrollBar                                                                   *

+ 8 - 8
src/main/resources/fxml/simple_dialog.fxml

@@ -1,16 +1,16 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
-<?import javafx.scene.control.Label?>
-<?import javafx.scene.layout.HBox?>
-<?import javafx.scene.control.Button?>
+<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
 <?import javafx.geometry.Insets?>
+<?import javafx.scene.control.Button?>
+<?import javafx.scene.control.ButtonBar?>
+<?import javafx.scene.control.Label?>
 <?import javafx.scene.Group?>
+<?import javafx.scene.layout.HBox?>
+<?import javafx.scene.layout.Region?>
 <?import javafx.scene.layout.StackPane?>
-<?import javafx.scene.shape.Circle?>
-<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
 <?import javafx.scene.layout.VBox?>
-<?import javafx.scene.layout.Region?>
-<?import javafx.scene.control.ButtonBar?>
+<?import javafx.scene.shape.Circle?>
 <HBox xmlns:fx="http://javafx.com/fxml"
 	  xmlns="http://javafx.com/javafx"
 	  fx:controller="org.cryptomator.ui.dialogs.SimpleDialogController"
@@ -41,7 +41,7 @@
 			<Region VBox.vgrow="ALWAYS" minHeight="18"/>
 			<ButtonBar buttonMinWidth="120" buttonOrder="+CI">
 				<buttons>
-					<Button text="${controller.cancelButtonText}" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#handleCancel"/>
+					<Button text="${controller.cancelButtonText}" ButtonBar.buttonData="CANCEL_CLOSE" cancelButton="true" onAction="#handleCancel" visible="${controller.cancelButtonVisible}" managed="${controller.cancelButtonVisible}"/>
 					<Button text="${controller.okButtonText}" ButtonBar.buttonData="FINISH" defaultButton="true" onAction="#handleOk"/>
 				</buttons>
 			</ButtonBar>

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

@@ -107,7 +107,6 @@ addvaultwizard.success.unlockNow=Unlock Now
 removeVault.title=Remove "%s"
 removeVault.message=Remove vault?
 removeVault.description=This will only make Cryptomator forget about this vault. You can add it again. No encrypted files will be deleted from your hard drive.
-removeVault.confirmBtn=Remove Vault
 
 # Change Password
 changepassword.title=Change Password
@@ -284,7 +283,7 @@ preferences.title=Preferences
 ## General
 preferences.general=General
 preferences.general.startHidden=Hide window when starting Cryptomator
-preferences.general.autoCloseVaults=Lock open vaults automatically when quitting application
+preferences.general.autoCloseVaults=Lock vaults without asking when quitting application
 preferences.general.debugLogging=Enable debug logging
 preferences.general.debugDirectory=Reveal log files
 preferences.general.autoStart=Launch Cryptomator on system start

+ 3 - 3
src/test/java/org/cryptomator/ui/addvaultwizard/ReadMeGeneratorTest.java

@@ -15,8 +15,8 @@ public class ReadMeGeneratorTest {
 	@ParameterizedTest
 	@CsvSource({ //
 			"test,test", //
-			"t\u00E4st,t\\u228st", //
-			"t\uD83D\uDE09st,t\\u55357\\u56841st", //
+			"t\u00E4st,t\\'E4st", //
+			"t\uD83D\uDE09st,t\\uc1\\u55357\\uc1\\u56841st", //
 	})
 	public void testEscapeNonAsciiChars(String input, String expectedResult) {
 		ReadmeGenerator readmeGenerator = new ReadmeGenerator(null);
@@ -40,7 +40,7 @@ public class ReadMeGeneratorTest {
 		MatcherAssert.assertThat(result, CoreMatchers.startsWith("{\\rtf1\\fbidis\\ansi\\uc0\\fs32"));
 		MatcherAssert.assertThat(result, CoreMatchers.containsString("{\\sa80 Dear User,}\\par"));
 		MatcherAssert.assertThat(result, CoreMatchers.containsString("{\\sa80 \\b please don't touch the \"d\" directory.}\\par "));
-		MatcherAssert.assertThat(result, CoreMatchers.containsString("{\\sa80 Thank you for your cooperation \\u55357\\u56841}\\par"));
+		MatcherAssert.assertThat(result, CoreMatchers.containsString("{\\sa80 Thank you for your cooperation \\uc1\\u55357\\uc1\\u56841}\\par"));
 		MatcherAssert.assertThat(result, CoreMatchers.endsWith("}"));
 	}