it is not added within the last {@value DEBOUNCE_THRESHOLD_SECONDS} seconds
*
*
+ * @see org.cryptomator.ui.fxapp.FxNotificationManager
*/
@Singleton
public class NotificationManager {
private static final int DEBOUNCE_THRESHOLD_SECONDS = 5;
- Cache eventCache;
- ConcurrentLinkedQueue eventsRequiringNotification;
+ private final Cache debounceCache;
+ private final ConcurrentLinkedQueue pendingEvents;
@Inject
public NotificationManager() {
- eventCache = Caffeine.newBuilder().expireAfterWrite(Duration.ofSeconds(DEBOUNCE_THRESHOLD_SECONDS)).build();
- eventsRequiringNotification = new ConcurrentLinkedQueue<>();
+ debounceCache = Caffeine.newBuilder().expireAfterWrite(Duration.ofSeconds(DEBOUNCE_THRESHOLD_SECONDS)).build();
+ pendingEvents = new ConcurrentLinkedQueue<>();
}
/**
@@ -55,9 +56,9 @@ public class NotificationManager {
boolean addEvent(Vault v, Path keyPath, FilesystemEvent e) {
var key = new FSEventBucket(v, keyPath, e.getClass());
var isAdded = new AtomicBoolean(false);
- eventCache.asMap().computeIfAbsent(key, _ -> {
+ debounceCache.asMap().computeIfAbsent(key, _ -> {
synchronized (this) {
- eventsRequiringNotification.add(new VaultEvent(v, e));
+ pendingEvents.add(new VaultEvent(v, e));
isAdded.set(true);
}
return e;
@@ -66,15 +67,15 @@ public class NotificationManager {
}
/**
- * Clones all events requiring a notification to the target list and clears afterward the notification manager queue
+ * Adds all events to the target list and clears afterward the pending-event-queue
*
- * @param target list the queue is cloned to
+ * @param target list where the filesystem events are copied to
* @return {@code true}, if elements were copied
*/
- public boolean cloneTo(List target) {
+ public boolean addTo(List target) {
synchronized (this) {
- var result = target.addAll(eventsRequiringNotification);
- eventsRequiringNotification.clear();
+ var result = target.addAll(pendingEvents);
+ pendingEvents.clear();
return result;
}
}
diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java
index 35b18bdc4..f33436a86 100644
--- a/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java
+++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplication.java
@@ -30,7 +30,7 @@ public class FxApplication {
private final FxApplicationTerminator applicationTerminator;
private final AutoUnlocker autoUnlocker;
private final FxFSEventList fxFSEventList;
- private final FxNotificationRadar notificationRadar;
+ private final FxNotificationManager notificationRadar;
@Inject
FxApplication(@Named("startupTime") long startupTime, //
@@ -43,7 +43,7 @@ public class FxApplication {
FxApplicationTerminator applicationTerminator, //
AutoUnlocker autoUnlocker, //
FxFSEventList fxFSEventList, //
- FxNotificationRadar notificationRadar) {
+ FxNotificationManager notificationRadar) {
this.startupTime = startupTime;
this.environment = environment;
this.settings = settings;
diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxNotificationRadar.java b/src/main/java/org/cryptomator/ui/fxapp/FxNotificationManager.java
similarity index 61%
rename from src/main/java/org/cryptomator/ui/fxapp/FxNotificationRadar.java
rename to src/main/java/org/cryptomator/ui/fxapp/FxNotificationManager.java
index e63ebbf4f..7be86fc50 100644
--- a/src/main/java/org/cryptomator/ui/fxapp/FxNotificationRadar.java
+++ b/src/main/java/org/cryptomator/ui/fxapp/FxNotificationManager.java
@@ -11,10 +11,18 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
- * Sends notifications
+ * Notification manager inside the UI domain.
+ *
+ * Polls the {@link NotificationManager} for pending events every {@value POLL_INTERVAL_SECONDS } seconds and
+ * triggers the notification window display when events are available.
+ * Returns an observable list of events requiring a user notification with {@link #getEventsRequiringNotification()}.
+ *
+ * @see NotificationManager
*/
@FxApplicationScoped
-public class FxNotificationRadar {
+public class FxNotificationManager {
+
+ private static final int POLL_INTERVAL_SECONDS = 1;
private final NotificationManager notificationManager;
private final ScheduledExecutorService scheduler;
@@ -22,7 +30,7 @@ public class FxNotificationRadar {
private final ObservableList eventsRequiringNotification;
@Inject
- public FxNotificationRadar(NotificationManager notificationManager, ScheduledExecutorService scheduler, FxApplicationWindows applicationWindows) {
+ public FxNotificationManager(NotificationManager notificationManager, ScheduledExecutorService scheduler, FxApplicationWindows applicationWindows) {
this.notificationManager = notificationManager;
this.scheduler = scheduler;
this.applicationWindows = applicationWindows;
@@ -30,15 +38,12 @@ public class FxNotificationRadar {
}
public void schedulePollForUpdates() {
- scheduler.scheduleAtFixedRate(this::checkForPendingNotifications, 0, 1000, TimeUnit.MILLISECONDS);
+ scheduler.scheduleAtFixedRate(this::checkForPendingNotifications, 0, POLL_INTERVAL_SECONDS, TimeUnit.SECONDS);
}
- /**
- * TODO
- */
private void checkForPendingNotifications() {
Platform.runLater(() -> {
- if (notificationManager.cloneTo(eventsRequiringNotification)) {
+ if (notificationManager.addTo(eventsRequiringNotification)) {
applicationWindows.showNotification();
}
});
diff --git a/src/main/java/org/cryptomator/ui/notification/NotificationController.java b/src/main/java/org/cryptomator/ui/notification/NotificationController.java
index 7bdf52541..47a807070 100644
--- a/src/main/java/org/cryptomator/ui/notification/NotificationController.java
+++ b/src/main/java/org/cryptomator/ui/notification/NotificationController.java
@@ -2,7 +2,7 @@ package org.cryptomator.ui.notification;
import org.cryptomator.event.VaultEvent;
import org.cryptomator.ui.common.FxController;
-import org.cryptomator.ui.fxapp.FxNotificationRadar;
+import org.cryptomator.ui.fxapp.FxNotificationManager;
import javax.inject.Inject;
import javafx.beans.binding.Bindings;
@@ -27,7 +27,7 @@ public class NotificationController implements FxController {
private static final String BUG_MSG = "IF YOU SEE THIS MESSAGE, PLEASE CONTACT THE DEVELOPERS OF CRYPTOMATOR ABOUT A BUG IN THE NOTIFICATION DISPLAY";
private final Stage window;
- private final SimpleListProperty notificationsProp;
+ private final SimpleListProperty events;
private final IntegerProperty selectionIndex;
private final ObservableStringValue paging;
private final ObjectProperty selectedEvent;
@@ -37,18 +37,12 @@ public class NotificationController implements FxController {
private final ExecutorService executorService;
@Inject
- public NotificationController(@NotificationWindow Stage window, FxNotificationRadar notificationRadar, ExecutorService executorService) {
+ public NotificationController(@NotificationWindow Stage window, FxNotificationManager notificationManager, ExecutorService executorService) {
this.window = window;
- this.notificationsProp = new SimpleListProperty<>(notificationRadar.getEventsRequiringNotification());
- this.selectionIndex = new SimpleIntegerProperty(0);
+ this.events = new SimpleListProperty<>(notificationManager.getEventsRequiringNotification());
+ this.selectionIndex = new SimpleIntegerProperty(-1);
this.selectedEvent = new SimpleObjectProperty<>();
- selectionIndex.addListener((_, _, n) -> {
- if (!notificationsProp.isEmpty()) {
- selectedEvent.setValue(notificationsProp.get(n.intValue()));
- }
- });
- selectedEvent.addListener(this::adjustTexts);
- this.paging = Bindings.createStringBinding(() -> selectionIndex.get() + 1 + "/" + notificationsProp.size(), selectionIndex, notificationsProp);
+ this.paging = Bindings.createStringBinding(() -> selectionIndex.get() + 1 + "/" + events.size(), selectionIndex, events);
this.message = new SimpleStringProperty();
this.description = new SimpleStringProperty();
this.actionText = new SimpleStringProperty();
@@ -57,11 +51,18 @@ public class NotificationController implements FxController {
@FXML
public void initialize() {
- selectedEvent.setValue(notificationsProp.get(selectionIndex.get()));
+ selectionIndex.addListener((_, _, n) -> {
+ if (!events.isEmpty()) {
+ selectedEvent.setValue(events.get(n.intValue()));
+ }
+ });
+ selectedEvent.addListener(this::selectTexts);
+
+ selectionIndex.setValue(0);
}
//TODO: Translations!
- private void adjustTexts(ObservableValue extends VaultEvent> observable, VaultEvent oldEvent, VaultEvent newEvent) {
+ private void selectTexts(ObservableValue extends VaultEvent> observable, VaultEvent oldEvent, VaultEvent newEvent) {
if (newEvent == null) {
message.set("NO CONTENT");
description.set(BUG_MSG);
@@ -80,7 +81,7 @@ public class NotificationController implements FxController {
@FXML
- public void handleButtonAction() {
+ public void processEvent() {
try {
var ev = selectedEvent.get();
switch (ev.actualEvent()) {
@@ -91,14 +92,14 @@ public class NotificationController implements FxController {
} finally {
//remove processed event
int i = selectionIndex.get();
- notificationsProp.remove(i);
- if (notificationsProp.isEmpty()) {
+ events.remove(i);
+ if (events.isEmpty()) {
close(); //no more events
- } else if (notificationsProp.size() == i) {
+ } else if (events.size() == i) {
i = i - 1;
selectionIndex.set(i); //triggers event update
} else {
- selectedEvent.set(notificationsProp.get(i));
+ selectedEvent.set(events.get(i));
}
}
}
@@ -114,14 +115,14 @@ public class NotificationController implements FxController {
@FXML
public void nextNotification() {
int i = selectionIndex.get();
- if (i != notificationsProp.size() - 1) {
+ if (i != events.size() - 1) {
selectionIndex.set(i + 1);
}
}
@FXML
public void close() {
- notificationsProp.clear();
+ events.clear();
window.close();
}
diff --git a/src/main/java/org/cryptomator/ui/notification/NotificationModule.java b/src/main/java/org/cryptomator/ui/notification/NotificationModule.java
index c6e8d6dd1..64895a40c 100644
--- a/src/main/java/org/cryptomator/ui/notification/NotificationModule.java
+++ b/src/main/java/org/cryptomator/ui/notification/NotificationModule.java
@@ -40,7 +40,21 @@ abstract class NotificationModule {
return stage;
}
- //TODO: TEST
+ /**
+ * Places the notification window on the screen according to some heuristic based on operating system and system bar placement.
+ *
+ * On macOS, the window is placed in the top-right corner of the primary screen, following platform conventions.
+ * On other operating systems, the window placement depends on the location of the system bar:
+ *
+ *
If the system bar is at the top, the window is centered horizontally at the top of the screen.
+ *
Otherwise (e.g., system bar at the bottom or elsewhere), the window is placed in the bottom-right corner.
+ *
+ *
+ * The method uses the visual bounds of the primary screen to avoid overlapping with system UI elements.
+ * Assumes the window size has already been set before calling this method.
+ *
+ * @param window the Stage representing the notification window to be placed
+ */
static void placeWindow(Stage window) {
var screen = Screen.getPrimary();
var vBounds = screen.getVisualBounds();
diff --git a/src/main/resources/fxml/notification.fxml b/src/main/resources/fxml/notification.fxml
index cd65d3579..646441e56 100644
--- a/src/main/resources/fxml/notification.fxml
+++ b/src/main/resources/fxml/notification.fxml
@@ -42,7 +42,7 @@
-