diff --git a/src/main/java/org/cryptomator/common/EventMap.java b/src/main/java/org/cryptomator/common/EventMap.java
deleted file mode 100644
index 2e8dbf035..000000000
--- a/src/main/java/org/cryptomator/common/EventMap.java
+++ /dev/null
@@ -1,160 +0,0 @@
-package org.cryptomator.common;
-
-import org.cryptomator.cryptofs.event.BrokenDirFileEvent;
-import org.cryptomator.cryptofs.event.BrokenFileNodeEvent;
-import org.cryptomator.cryptofs.event.ConflictResolutionFailedEvent;
-import org.cryptomator.cryptofs.event.ConflictResolvedEvent;
-import org.cryptomator.cryptofs.event.DecryptionFailedEvent;
-import org.cryptomator.cryptofs.event.FilesystemEvent;
-import org.cryptomator.event.VaultEvent;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-import javafx.beans.InvalidationListener;
-import javafx.collections.FXCollections;
-import javafx.collections.MapChangeListener;
-import javafx.collections.ObservableMap;
-import java.nio.file.Path;
-import java.util.Collection;
-import java.util.Comparator;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Map containing {@link VaultEvent}s.
- * The map is keyed by the ciphertext path of the affected resource _and_ the {@link FilesystemEvent}s class in order to group same events
- *
- * Use {@link EventMap#put(VaultEvent)} to add an element and {@link EventMap#remove(VaultEvent)} to remove it.
- *
- * The map is size restricted to {@value MAX_SIZE} elements. If a _new_ element (i.e. not already present) is added, the least recently added is removed.
- */
-@Singleton
-public class EventMap implements ObservableMap {
-
- private static final int MAX_SIZE = 300;
-
- public record EventKey(Path ciphertextPath, Class extends FilesystemEvent> c) {}
-
- private final ObservableMap delegate;
-
- @Inject
- public EventMap() {
- delegate = FXCollections.observableHashMap();
- }
-
- @Override
- public void addListener(MapChangeListener super EventKey, ? super VaultEvent> mapChangeListener) {
- delegate.addListener(mapChangeListener);
- }
-
- @Override
- public void removeListener(MapChangeListener super EventKey, ? super VaultEvent> mapChangeListener) {
- delegate.removeListener(mapChangeListener);
- }
-
- @Override
- public int size() {
- return delegate.size();
- }
-
- @Override
- public boolean isEmpty() {
- return delegate.isEmpty();
- }
-
- @Override
- public boolean containsKey(Object key) {
- return delegate.containsKey(key);
- }
-
- @Override
- public boolean containsValue(Object value) {
- return delegate.containsValue(value);
- }
-
- @Override
- public VaultEvent get(Object key) {
- return delegate.get(key);
- }
-
- @Override
- public @Nullable VaultEvent put(EventKey key, VaultEvent value) {
- return delegate.put(key, value);
- }
-
- @Override
- public VaultEvent remove(Object key) {
- return delegate.remove(key);
- }
-
- @Override
- public void putAll(@NotNull Map extends EventKey, ? extends VaultEvent> m) {
- delegate.putAll(m);
- }
-
- @Override
- public void clear() {
- delegate.clear();
- }
-
- @Override
- public @NotNull Set keySet() {
- return delegate.keySet();
- }
-
- @Override
- public @NotNull Collection values() {
- return delegate.values();
- }
-
- @Override
- public @NotNull Set> entrySet() {
- return delegate.entrySet();
- }
-
- @Override
- public void addListener(InvalidationListener invalidationListener) {
- delegate.addListener(invalidationListener);
- }
-
- @Override
- public void removeListener(InvalidationListener invalidationListener) {
- delegate.removeListener(invalidationListener);
- }
-
- public synchronized void put(VaultEvent e) {
- //compute key
- var key = computeKey(e.actualEvent());
- //if-else
- var nullOrEntry = delegate.get(key);
- if (nullOrEntry == null) {
- if (size() == MAX_SIZE) {
- delegate.entrySet().stream() //
- .min(Comparator.comparing(entry -> entry.getValue().actualEvent().getTimestamp())) //
- .ifPresent(oldestEntry -> delegate.remove(oldestEntry.getKey()));
- }
- delegate.put(key, e);
- } else {
- delegate.put(key, nullOrEntry.incrementCount(e.actualEvent()));
- }
- }
-
- public synchronized VaultEvent remove(VaultEvent similar) {
- //compute key
- var key = computeKey(similar.actualEvent());
- return this.remove(key);
- }
-
- private EventKey computeKey(FilesystemEvent e) {
- var p = switch (e) {
- case DecryptionFailedEvent(_, Path ciphertextPath, _) -> ciphertextPath;
- case ConflictResolvedEvent(_, _, _, _, Path resolvedCiphertext) -> resolvedCiphertext;
- case ConflictResolutionFailedEvent(_, _, Path conflictingCiphertext, _) -> conflictingCiphertext;
- case BrokenDirFileEvent(_, Path ciphertext) -> ciphertext;
- case BrokenFileNodeEvent(_, _, Path ciphertext) -> ciphertext;
- };
- return new EventKey(p, e.getClass());
- }
-}
diff --git a/src/main/java/org/cryptomator/event/NotificationManager.java b/src/main/java/org/cryptomator/event/NotificationManager.java
new file mode 100644
index 000000000..36b9eb655
--- /dev/null
+++ b/src/main/java/org/cryptomator/event/NotificationManager.java
@@ -0,0 +1,75 @@
+package org.cryptomator.event;
+
+import com.github.benmanes.caffeine.cache.Cache;
+import com.github.benmanes.caffeine.cache.Caffeine;
+import org.cryptomator.cryptofs.event.BrokenFileNodeEvent;
+import org.cryptomator.cryptofs.event.FilesystemEvent;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import java.nio.file.Path;
+import java.time.Duration;
+import java.util.ArrayDeque;
+import java.util.List;
+import java.util.Queue;
+
+/**
+ * Manager for notifications.
+ *
+ * To add (filesystem) events, use method {@link #tryAddEvent(FilesystemEvent)}. If the input event is eligible, it is added to an internal queue.
+ * An event is eligible, if
+ *
+ * the event should trigger a notification and
+ * it is not added within the last {@value DEBOUNCE_THRESHOLD_SECONDS} seconds
+ *
+ *
+ */
+@Singleton
+public class NotificationManager {
+
+ private static final int DEBOUNCE_THRESHOLD_SECONDS = 5;
+
+ Cache eventCache;
+ Queue eventsRequiringNotification;
+
+ @Inject
+ public NotificationManager() {
+ eventCache = Caffeine.newBuilder().expireAfterWrite(Duration.ofSeconds(DEBOUNCE_THRESHOLD_SECONDS)).build();
+ eventsRequiringNotification = new ArrayDeque<>();
+ }
+
+ public boolean tryAddEvent(FilesystemEvent e) {
+ var notRecentlyAdded = switch (e) {
+ case BrokenFileNodeEvent bfne -> isRecent(bfne.ciphertextPath(), bfne);
+ default -> false;
+ };
+
+ if(notRecentlyAdded) {
+ synchronized (this) {
+ eventsRequiringNotification.add(e);
+ }
+
+ }
+ return notRecentlyAdded;
+ }
+
+ boolean isRecent(Path key, FilesystemEvent e) {
+ var cacheElement = eventCache.get(key, _ -> e);
+ return cacheElement == e;
+ }
+
+ /**
+ * Clones all events requiring a notification to the target list and clears afterward the notification manager queue
+ * @param target list the queue is cloned to
+ * @return {@code true}, if elements were copied
+ */
+ public boolean cloneTo(List target) {
+ synchronized (this) {
+ var result = target.addAll(eventsRequiringNotification);
+ eventsRequiringNotification.clear();
+ return result;
+ }
+ }
+
+
+}
diff --git a/src/main/java/org/cryptomator/ipc/HandleNotificationCallbackMessage.java b/src/main/java/org/cryptomator/ipc/HandleNotificationCallbackMessage.java
deleted file mode 100644
index cfd8cfdb2..000000000
--- a/src/main/java/org/cryptomator/ipc/HandleNotificationCallbackMessage.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package org.cryptomator.ipc;
-
-import java.nio.ByteBuffer;
-import java.nio.charset.StandardCharsets;
-
-record HandleNotificationCallbackMessage(String content) implements IpcMessage {
-
- @Override
- public MessageType getMessageType() {
- return MessageType.HANDLE_NOTIFICATION_CALLBACK;
- }
-
- @Override
- public ByteBuffer encodePayload() {
- return ByteBuffer.wrap(content.getBytes(StandardCharsets.UTF_8));
- }
-
- public static IpcMessage decode(ByteBuffer byteBuffer) {
- var content = StandardCharsets.UTF_8.decode(byteBuffer).toString();
- return new HandleNotificationCallbackMessage(content);
- }
-}
diff --git a/src/main/java/org/cryptomator/ipc/IpcMessage.java b/src/main/java/org/cryptomator/ipc/IpcMessage.java
index 928f79c69..7f76da295 100644
--- a/src/main/java/org/cryptomator/ipc/IpcMessage.java
+++ b/src/main/java/org/cryptomator/ipc/IpcMessage.java
@@ -10,12 +10,11 @@ import java.nio.channels.WritableByteChannel;
import java.util.function.Function;
//TODO can the enum be removed?
-sealed interface IpcMessage permits HandleLaunchArgsMessage, RevealRunningAppMessage, HandleNotificationCallbackMessage {
+sealed interface IpcMessage permits HandleLaunchArgsMessage, RevealRunningAppMessage {
enum MessageType {
REVEAL_RUNNING_APP(RevealRunningAppMessage::decode),
- HANDLE_LAUNCH_ARGS(HandleLaunchArgsMessage::decode),
- HANDLE_NOTIFICATION_CALLBACK(HandleNotificationCallbackMessage::decode);
+ HANDLE_LAUNCH_ARGS(HandleLaunchArgsMessage::decode);
private final Function decoder;
diff --git a/src/main/java/org/cryptomator/ipc/IpcMessageListener.java b/src/main/java/org/cryptomator/ipc/IpcMessageListener.java
index bab4c2e8e..756305cbe 100644
--- a/src/main/java/org/cryptomator/ipc/IpcMessageListener.java
+++ b/src/main/java/org/cryptomator/ipc/IpcMessageListener.java
@@ -8,7 +8,6 @@ public interface IpcMessageListener {
switch (message) {
case RevealRunningAppMessage m -> revealRunningApp(); // TODO: rename to _ with JEP 443
case HandleLaunchArgsMessage m -> handleLaunchArgs(m.args());
- case HandleNotificationCallbackMessage m -> handleNotificationCallback(m.content());
}
}
@@ -16,6 +15,4 @@ public interface IpcMessageListener {
void handleLaunchArgs(List args);
- void handleNotificationCallback(String content);
-
}
diff --git a/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java b/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java
index 348b3a26b..b479a7f14 100644
--- a/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java
+++ b/src/main/java/org/cryptomator/ui/controls/FontAwesome5Icon.java
@@ -12,6 +12,8 @@ public enum FontAwesome5Icon {
CARET_DOWN("\uF0D7"), //
CARET_RIGHT("\uF0Da"), //
CHECK("\uF00C"), //
+ CHEVRON_LEFT("\uF053"), //
+ CHEVRON_RIGHT("\uF054"), //
CLOCK("\uF017"), //
CLIPBOARD("\uF328"), //
COG("\uF013"), //
diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java
index ebc7cb635..ae9e60388 100644
--- a/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java
+++ b/src/main/java/org/cryptomator/ui/fxapp/FxApplicationWindows.java
@@ -4,7 +4,6 @@ import com.google.common.base.Preconditions;
import dagger.Lazy;
import org.cryptomator.common.vaults.Vault;
import org.cryptomator.common.vaults.VaultState;
-import org.cryptomator.integrations.notify.NotifyService;
import org.cryptomator.integrations.tray.TrayIntegrationProvider;
import org.cryptomator.ui.dialogs.Dialogs;
import org.cryptomator.ui.dialogs.SimpleDialog;
@@ -198,8 +197,8 @@ public class FxApplicationWindows {
return CompletableFuture.supplyAsync(() -> eventViewWindow.get().showEventViewerWindow(), Platform::runLater).whenComplete(this::reportErrors);
}
- public CompletionStage showNotification(Runnable action) {
- return CompletableFuture.supplyAsync(() -> notificationWindow.create(message, description, action).showNotification(), Platform::runLater).whenComplete(this::reportErrors);
+ public CompletionStage showNotification() {
+ return CompletableFuture.supplyAsync(() -> notificationWindow.create().showNotification(), Platform::runLater).whenComplete(this::reportErrors);
}
/**
diff --git a/src/main/java/org/cryptomator/ui/fxapp/FxNotificationRadar.java b/src/main/java/org/cryptomator/ui/fxapp/FxNotificationRadar.java
index 66bd08488..d7aadb103 100644
--- a/src/main/java/org/cryptomator/ui/fxapp/FxNotificationRadar.java
+++ b/src/main/java/org/cryptomator/ui/fxapp/FxNotificationRadar.java
@@ -1,21 +1,52 @@
package org.cryptomator.ui.fxapp;
+import org.cryptomator.cryptofs.event.FilesystemEvent;
+import org.cryptomator.event.NotificationManager;
+
import javax.inject.Inject;
+import javafx.application.Platform;
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
/**
- * Scans the event list for events requiring a notification
+ * Sends notifications
*/
@FxApplicationScoped
public class FxNotificationRadar {
- private final FxFSEventList eventList;
+ private final NotificationManager notificationManager;
+ private final ScheduledExecutorService scheduler;
+ private final FxApplicationWindows applicationWindows;
+ private final ObservableList eventsRequiringNotification;
@Inject
- FxNotificationRadar(FxFSEventList eventList) {
- this.eventList = eventList;
- eventList.getObservableList()
+ public FxNotificationRadar(NotificationManager notificationManager, ScheduledExecutorService scheduler, FxApplicationWindows applicationWindows) {
+ this.notificationManager = notificationManager;
+ this.scheduler = scheduler;
+ this.applicationWindows = applicationWindows;
+ this.eventsRequiringNotification = FXCollections.observableArrayList();
+ }
+
+ public void schedulePollForUpdates() {
+ scheduler.schedule(this::checkForPendingNotifications, 1000, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * TODO
+ */
+ private void checkForPendingNotifications() {
+ Platform.runLater(() -> {
+ if (notificationManager.cloneTo(eventsRequiringNotification)) {
+ applicationWindows.showNotification();
+ }
+ });
}
+ public ObservableList getEventsRequiringNotification() {
+ return eventsRequiringNotification;
+ }
}
diff --git a/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java b/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java
index dfdc8f338..5a375e9b1 100644
--- a/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java
+++ b/src/main/java/org/cryptomator/ui/mainwindow/VaultListController.java
@@ -10,15 +10,15 @@ import org.cryptomator.common.vaults.VaultListManager;
import org.cryptomator.cryptofs.CryptoFileSystemProvider;
import org.cryptomator.cryptofs.DirStructure;
import org.cryptomator.cryptofs.common.Constants;
+import org.cryptomator.cryptofs.event.BrokenDirFileEvent;
import org.cryptomator.integrations.mount.MountService;
-import org.cryptomator.integrations.notify.NotifyService;
-import org.cryptomator.integrations.notify.NotifyServiceException;
import org.cryptomator.ui.addvaultwizard.AddVaultWizardComponent;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.VaultService;
import org.cryptomator.ui.dialogs.Dialogs;
-import org.cryptomator.ui.fxapp.FxFSEventList;
import org.cryptomator.ui.fxapp.FxApplicationWindows;
+import org.cryptomator.ui.fxapp.FxFSEventList;
+import org.cryptomator.ui.fxapp.FxNotificationRadar;
import org.cryptomator.ui.preferences.SelectedPreferencesTab;
import org.cryptomator.ui.recoverykey.RecoveryKeyComponent;
import org.slf4j.Logger;
@@ -35,7 +35,6 @@ import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
-import javafx.geometry.Side;
import javafx.scene.control.Button;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.ListView;
@@ -82,6 +81,7 @@ public class VaultListController implements FxController {
private final AddVaultWizardComponent.Builder addVaultWizard;
private final BooleanBinding emptyVaultList;
private final BooleanProperty unreadEvents;
+ private final FxNotificationRadar notificationRadar;
private final VaultListManager vaultListManager;
private final BooleanProperty draggingVaultOver = new SimpleBooleanProperty();
private final ResourceBundle resourceBundle;
@@ -115,7 +115,9 @@ public class VaultListController implements FxController {
RecoveryKeyComponent.Factory recoveryKeyWindow, //
VaultComponent.Factory vaultComponentFactory, //
List mountServices, //
- FxFSEventList fxFSEventList) {
+ FxFSEventList fxFSEventList,
+ FxNotificationRadar notificationRadar
+ ) {
this.mainWindow = mainWindow;
this.vaults = vaults;
this.selectedVault = selectedVault;
@@ -132,6 +134,7 @@ public class VaultListController implements FxController {
this.emptyVaultList = Bindings.isEmpty(vaults);
this.unreadEvents = fxFSEventList.unreadEventsProperty();
+ this.notificationRadar = notificationRadar;
selectedVault.addListener(this::selectedVaultDidChange);
cellSize = settings.compactMode.map(compact -> compact ? 30.0 : 60.0);
@@ -207,6 +210,8 @@ public class VaultListController implements FxController {
@FXML
private void toggleMenu() {
+ notificationRadar.getEventsRequiringNotification().add(new BrokenDirFileEvent(Path.of("C:\\Your\\Momma\\Does\\Things")));
+ appWindows.showNotification();
/*
if (addVaultContextMenu.isShowing()) {
addVaultContextMenu.hide();
@@ -214,15 +219,6 @@ public class VaultListController implements FxController {
addVaultContextMenu.show(addVaultButton, Side.BOTTOM, 0.0, 0.0);
}
*/
- NotifyService.loadAll().findFirst().ifPresent(
- s -> {
- try {
- s.sendNotification("Hello", "Lindsay");
- } catch (NotifyServiceException e) {
- throw new RuntimeException(e);
- }
- }
- );
}
private void deselect(MouseEvent released) {
diff --git a/src/main/java/org/cryptomator/ui/notification/NotificationComponent.java b/src/main/java/org/cryptomator/ui/notification/NotificationComponent.java
index 7a9d2c0b9..3ebcdf4ab 100644
--- a/src/main/java/org/cryptomator/ui/notification/NotificationComponent.java
+++ b/src/main/java/org/cryptomator/ui/notification/NotificationComponent.java
@@ -1,8 +1,11 @@
package org.cryptomator.ui.notification;
-import dagger.BindsInstance;
+import dagger.Lazy;
import dagger.Subcomponent;
+import org.cryptomator.ui.common.FxmlFile;
+import org.cryptomator.ui.common.FxmlScene;
+import javafx.scene.Scene;
import javafx.stage.Stage;
@NotificationScoped
@@ -10,17 +13,23 @@ import javafx.stage.Stage;
public interface NotificationComponent {
@NotificationWindow
- Stage notificationWindow();
+ Stage window();
- default Stage showNotification(){
- var window = notificationWindow();
+ @FxmlScene(FxmlFile.NOTIFICATION)
+ Lazy scene();
+
+ default Stage showNotification() {
+ var window = window();
+ window.setScene(scene().get());
+ window.sizeToScene();
window.show();
+ window.requestFocus();
return window;
}
@Subcomponent.Factory
interface Factory {
- NotificationComponent create(@BindsInstance Runnable action);
+ NotificationComponent create();
}
}
diff --git a/src/main/java/org/cryptomator/ui/notification/NotificationController.java b/src/main/java/org/cryptomator/ui/notification/NotificationController.java
index c35a754f6..9e60ceaa7 100644
--- a/src/main/java/org/cryptomator/ui/notification/NotificationController.java
+++ b/src/main/java/org/cryptomator/ui/notification/NotificationController.java
@@ -1,56 +1,146 @@
package org.cryptomator.ui.notification;
-import org.cryptomator.common.Nullable;
-import org.cryptomator.integrations.notify.NotifyService;
+import org.cryptomator.cryptofs.event.FilesystemEvent;
import org.cryptomator.ui.common.FxController;
+import org.cryptomator.ui.fxapp.FxNotificationRadar;
import javax.inject.Inject;
-import javax.inject.Named;
-import javafx.event.ActionEvent;
+import javafx.beans.binding.BooleanBinding;
+import javafx.beans.property.IntegerProperty;
+import javafx.beans.property.ObjectProperty;
+import javafx.beans.property.SimpleIntegerProperty;
+import javafx.beans.property.SimpleListProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.beans.property.SimpleStringProperty;
+import javafx.beans.property.StringProperty;
+import javafx.beans.value.ObservableBooleanValue;
+import javafx.beans.value.ObservableIntegerValue;
+import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
-import javafx.scene.control.Button;
+import javafx.stage.Stage;
+import java.util.Objects;
import java.util.concurrent.ExecutorService;
@NotificationScoped
public class NotificationController implements FxController {
- private final String message;
- private final String description;
- private final NotifyAction callback;
+ private static final String LOREM_IPSUM = """
+ Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam""";
+
+ private final Stage window;
+ private final SimpleListProperty notificationsProp;
+ private final IntegerProperty selectionIndex;
+ private final ObjectProperty selectedEvent;
+ private final StringProperty message;
+ private final StringProperty description;
+ private final StringProperty actionText;
private final ExecutorService executorService;
- @FXML
- Button button;
-
@Inject
- public NotificationController(@Named("Message") String message, @Named("Description") String description, @Nullable NotifyAction callback, ExecutorService executorService) {
- this.message = message;
- this.description = description;
- this.callback = callback;
+ public NotificationController(@NotificationWindow Stage window, FxNotificationRadar notificationRadar, ExecutorService executorService) {
+ this.window = window;
+ this.notificationsProp = new SimpleListProperty<>(notificationRadar.getEventsRequiringNotification());
+ this.selectionIndex = new SimpleIntegerProperty(0);
+ this.selectedEvent = new SimpleObjectProperty<>();
+ selectionIndex.addListener((obs, o, n) -> selectedEvent.setValue(notificationsProp.get(n.intValue())));
+ selectedEvent.addListener(this::adjustTexts);
+ this.message = new SimpleStringProperty();
+ this.description = new SimpleStringProperty();
+ this.actionText = new SimpleStringProperty();
this.executorService = executorService;
}
+ @FXML
+ public void initialize() {
+ selectedEvent.setValue(notificationsProp.get(selectionIndex.get()));
+ }
+
+ private void adjustTexts(ObservableValue extends FilesystemEvent> observable, FilesystemEvent oldEvent, FilesystemEvent newEvent) {
+ switch (newEvent) {
+ default -> {
+ message.set("BABA");
+ description.set(LOREM_IPSUM);
+ actionText.set("ACTION");
+ }
+ }
+ }
+
+
+ @FXML
+ public void handleButtonAction() {
+ var ev = selectedEvent.get();
+ switch (ev) {
+ default -> {
+ } //normally nothing
+ }
+ //executorService.submit(callback.action());
+ int i = selectionIndex.get();
+ notificationsProp.remove(i); //remove processed event
+
+ if (notificationsProp.isEmpty()) {
+ close(); //no more events
+ } else if (notificationsProp.size() == i) {
+ i = i - 1;
+ selectionIndex.set(i); //triggers event update
+ } else {
+ selectedEvent.set(notificationsProp.get(i));
+ }
+ }
+
+ @FXML
+ public void close() {
+ notificationsProp.clear();
+ window.close();
+ }
+
//FXML bindings
-
- public String getMessage() {
+ public ObservableValue messageProperty() {
return message;
}
- public String getDescription() {
+ public String getMessage() {
+ return message.get();
+ }
+
+ public ObservableValue descriptionProperty() {
return description;
}
- public String getButtonText() {
- return callback == null? "" : callback.label();
+ public String getDescription() {
+ return description.get();
+ }
+
+ public ObservableValue actionTextProperty() {
+ return actionText;
+ }
+
+ public String getActionText() {
+ return Objects.requireNonNullElse(actionText.get(), "");
+ }
+
+ public ObservableBooleanValue buttonVisibleProperty() {
+ return actionText.isEmpty();
}
public boolean isButtonVisible() {
- return callback != null;
+ return actionText.get() != null;
}
- @FXML
- public void handleButtonAction(ActionEvent actionEvent) {
- executorService.submit(callback.action());
+ public ObservableIntegerValue selectionIndexProperty() {
+ return selectionIndex;
}
+
+ public int getSelectionIndex() {
+ return selectionIndex.get();
+ }
+
+ public ObservableIntegerValue numberOfNotificationsProperty() {
+ return notificationsProp.sizeProperty();
+ }
+
+ public int getNumberOfNotifications() {
+ return notificationsProp.size();
+ }
+
}
diff --git a/src/main/java/org/cryptomator/ui/notification/NotificationModule.java b/src/main/java/org/cryptomator/ui/notification/NotificationModule.java
index 3540e7636..68508f8b6 100644
--- a/src/main/java/org/cryptomator/ui/notification/NotificationModule.java
+++ b/src/main/java/org/cryptomator/ui/notification/NotificationModule.java
@@ -4,6 +4,7 @@ import dagger.Binds;
import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoMap;
+import org.apache.commons.lang3.SystemUtils;
import org.cryptomator.ui.common.DefaultSceneFactory;
import org.cryptomator.ui.common.FxController;
import org.cryptomator.ui.common.FxControllerKey;
@@ -11,11 +12,11 @@ import org.cryptomator.ui.common.FxmlFile;
import org.cryptomator.ui.common.FxmlLoaderFactory;
import org.cryptomator.ui.common.FxmlScene;
import org.cryptomator.ui.common.StageInitializer;
-import org.cryptomator.ui.quit.QuitForcedController;
import javax.inject.Provider;
import javafx.scene.Scene;
import javafx.stage.Modality;
+import javafx.stage.Screen;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import java.util.Map;
@@ -27,29 +28,46 @@ abstract class NotificationModule {
@Provides
@NotificationWindow
@NotificationScoped
- static Stage provideStage(StageInitializer initializer, @FxmlScene(FxmlFile.NOTIFICATION) Scene notificationScene) {
- Stage stage = new Stage(StageStyle.UTILITY);
+ static Stage provideStage(StageInitializer initializer) {
+ Stage stage = new Stage(StageStyle.TRANSPARENT);
stage.setTitle("Filesystem notification"); //TODO: translate
stage.setResizable(false);
stage.initModality(Modality.NONE);
+ stage.setAlwaysOnTop(true);
initializer.accept(stage);
- stage.setScene(notificationScene);
- stage.sizeToScene();
+ stage.setOnShown(_ -> placeWindow(stage) );
return stage;
}
+ static void placeWindow(Stage window) {
+ if(SystemUtils.IS_OS_WINDOWS) { //place to right bottom
+ var screenBounds = Screen.getPrimary().getVisualBounds();
+ window.setX(screenBounds.getMaxX() - window.getWidth());
+ window.setY(screenBounds.getMaxY() - window.getHeight());
+ } else if(SystemUtils.IS_OS_MAC) { //place to right top
+ var screenBounds = Screen.getPrimary().getVisualBounds(); //TODO: TEST
+ window.setX(screenBounds.getMaxX() - window.getWidth());
+ window.setY(screenBounds.getMinY() - window.getHeight());
+ } else { //place to middle top
+ //GNOME; KDE; etc...
+ var screenBounds = Screen.getPrimary().getVisualBounds(); //TODO: TEST
+ window.setX(screenBounds.getMaxX() / 2.0);
+ window.setY(screenBounds.getMinY() - window.getHeight());
+ }
+ }
// javafx setup
@Provides
@FxmlScene(FxmlFile.NOTIFICATION)
@NotificationScoped
- static Scene provideNotificationScene(FxmlLoaderFactory fxmlLoaders) {
+ static Scene provideNotificationScene(@NotificationWindow FxmlLoaderFactory fxmlLoaders) {
return fxmlLoaders.createScene(FxmlFile.NOTIFICATION);
}
@Provides
@NotificationScoped
+ @NotificationWindow
static FxmlLoaderFactory provideFxmlLoaderFactory(Map, Provider> factories, DefaultSceneFactory sceneFactory, ResourceBundle resourceBundle) {
return new FxmlLoaderFactory(factories, sceneFactory, resourceBundle);
}
diff --git a/src/main/resources/css/light_theme.css b/src/main/resources/css/light_theme.css
index 39e2892ac..a2e88c29a 100644
--- a/src/main/resources/css/light_theme.css
+++ b/src/main/resources/css/light_theme.css
@@ -1186,3 +1186,12 @@
-fx-background-color: MAIN_BG;
-fx-font-size: 1.166667em; /* 14pt - 2 more than the default font */
}
+
+/**
+ Notification Window
+**/
+.notification-window {
+ -fx-background-radius: 18 18 18 18;
+ -fx-border-radius: 18 18 18 18;
+ -fx-background-color: MAIN_BG;
+}
\ No newline at end of file
diff --git a/src/main/resources/fxml/notification.fxml b/src/main/resources/fxml/notification.fxml
index f291d0c79..5f24eb25b 100644
--- a/src/main/resources/fxml/notification.fxml
+++ b/src/main/resources/fxml/notification.fxml
@@ -1,39 +1,53 @@
+
+
-
-
-
-
-
-
+
+
-
+
-
-
+
+
+
-
-
-
-
-
-
+ maxHeight="200.0" maxWidth="400.0" styleClass="notification-window" spacing="6">
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+