mirror of
https://github.com/cryptomator/cryptomator.git
synced 2026-05-22 04:31:27 +00:00
@@ -23,19 +23,20 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
* <li>it is not added within the last {@value DEBOUNCE_THRESHOLD_SECONDS} seconds</li>
|
||||
* </ul>
|
||||
*
|
||||
* @see org.cryptomator.ui.fxapp.FxNotificationManager
|
||||
*/
|
||||
@Singleton
|
||||
public class NotificationManager {
|
||||
|
||||
private static final int DEBOUNCE_THRESHOLD_SECONDS = 5;
|
||||
|
||||
Cache<FSEventBucket, FilesystemEvent> eventCache;
|
||||
ConcurrentLinkedQueue<VaultEvent> eventsRequiringNotification;
|
||||
private final Cache<FSEventBucket, FilesystemEvent> debounceCache;
|
||||
private final ConcurrentLinkedQueue<VaultEvent> 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<VaultEvent> target) {
|
||||
public boolean addTo(List<VaultEvent> target) {
|
||||
synchronized (this) {
|
||||
var result = target.addAll(eventsRequiringNotification);
|
||||
eventsRequiringNotification.clear();
|
||||
var result = target.addAll(pendingEvents);
|
||||
pendingEvents.clear();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -11,10 +11,18 @@ import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Sends notifications
|
||||
* Notification manager inside the UI domain.
|
||||
* <p>
|
||||
* 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<VaultEvent> 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();
|
||||
}
|
||||
});
|
||||
@@ -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<VaultEvent> notificationsProp;
|
||||
private final SimpleListProperty<VaultEvent> events;
|
||||
private final IntegerProperty selectionIndex;
|
||||
private final ObservableStringValue paging;
|
||||
private final ObjectProperty<VaultEvent> 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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
* <p>
|
||||
* 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:
|
||||
* <ul>
|
||||
* <li>If the system bar is at the top, the window is centered horizontally at the top of the screen.</li>
|
||||
* <li>Otherwise (e.g., system bar at the bottom or elsewhere), the window is placed in the bottom-right corner.</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* 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();
|
||||
|
||||
Reference in New Issue
Block a user