Skip to content

Commit 2471175

Browse files
committed
Streamlined UI getting rid of the notifications popover in favor of the notification overlay UI previously just used for temporary notifications.
1 parent 13d3bca commit 2471175

File tree

9 files changed

+250
-243
lines changed

9 files changed

+250
-243
lines changed

CodeEdit.xcodeproj/project.pbxproj

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -582,11 +582,9 @@
582582
B685DE7929CC9CCD002860C8 /* StatusBarIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = B685DE7829CC9CCD002860C8 /* StatusBarIcon.swift */; };
583583
B68DE5DF2D5A61E5009A43EF /* CENotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68DE5D72D5A61E5009A43EF /* CENotification.swift */; };
584584
B68DE5E02D5A61E5009A43EF /* NotificationBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68DE5D92D5A61E5009A43EF /* NotificationBannerView.swift */; };
585-
B68DE5E12D5A61E5009A43EF /* NotificationListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68DE5DA2D5A61E5009A43EF /* NotificationListView.swift */; };
586585
B68DE5E22D5A61E5009A43EF /* NotificationToolbarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68DE5DB2D5A61E5009A43EF /* NotificationToolbarItem.swift */; };
587586
B68DE5E32D5A61E5009A43EF /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68DE5DD2D5A61E5009A43EF /* NotificationManager.swift */; };
588587
B68DE5E52D5A7988009A43EF /* NotificationOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68DE5E42D5A7988009A43EF /* NotificationOverlayView.swift */; };
589-
B68DE5E72D5A7D62009A43EF /* NotificationBannerEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68DE5E62D5A7D62009A43EF /* NotificationBannerEnvironment.swift */; };
590588
B6966A282C2F683300259C2D /* SourceControlPullView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6966A272C2F683300259C2D /* SourceControlPullView.swift */; };
591589
B6966A2A2C2F687A00259C2D /* SourceControlFetchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6966A292C2F687A00259C2D /* SourceControlFetchView.swift */; };
592590
B6966A2E2C3056AD00259C2D /* SourceControlCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6966A2D2C3056AD00259C2D /* SourceControlCommands.swift */; };
@@ -1279,11 +1277,9 @@
12791277
B685DE7829CC9CCD002860C8 /* StatusBarIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusBarIcon.swift; sourceTree = "<group>"; };
12801278
B68DE5D72D5A61E5009A43EF /* CENotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CENotification.swift; sourceTree = "<group>"; };
12811279
B68DE5D92D5A61E5009A43EF /* NotificationBannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationBannerView.swift; sourceTree = "<group>"; };
1282-
B68DE5DA2D5A61E5009A43EF /* NotificationListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationListView.swift; sourceTree = "<group>"; };
12831280
B68DE5DB2D5A61E5009A43EF /* NotificationToolbarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationToolbarItem.swift; sourceTree = "<group>"; };
12841281
B68DE5DD2D5A61E5009A43EF /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = "<group>"; };
12851282
B68DE5E42D5A7988009A43EF /* NotificationOverlayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationOverlayView.swift; sourceTree = "<group>"; };
1286-
B68DE5E62D5A7D62009A43EF /* NotificationBannerEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationBannerEnvironment.swift; sourceTree = "<group>"; };
12871283
B6966A272C2F683300259C2D /* SourceControlPullView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlPullView.swift; sourceTree = "<group>"; };
12881284
B6966A292C2F687A00259C2D /* SourceControlFetchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlFetchView.swift; sourceTree = "<group>"; };
12891285
B6966A2D2C3056AD00259C2D /* SourceControlCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlCommands.swift; sourceTree = "<group>"; };
@@ -3574,9 +3570,7 @@
35743570
B68DE5DC2D5A61E5009A43EF /* Views */ = {
35753571
isa = PBXGroup;
35763572
children = (
3577-
B68DE5E62D5A7D62009A43EF /* NotificationBannerEnvironment.swift */,
35783573
B68DE5D92D5A61E5009A43EF /* NotificationBannerView.swift */,
3579-
B68DE5DA2D5A61E5009A43EF /* NotificationListView.swift */,
35803574
B68DE5DB2D5A61E5009A43EF /* NotificationToolbarItem.swift */,
35813575
B68DE5E42D5A7988009A43EF /* NotificationOverlayView.swift */,
35823576
);
@@ -4112,7 +4106,6 @@
41124106
04BA7C1C2AE2D84100584E1C /* GitClient+Commit.swift in Sources */,
41134107
B68DE5DF2D5A61E5009A43EF /* CENotification.swift in Sources */,
41144108
B68DE5E02D5A61E5009A43EF /* NotificationBannerView.swift in Sources */,
4115-
B68DE5E12D5A61E5009A43EF /* NotificationListView.swift in Sources */,
41164109
B68DE5E22D5A61E5009A43EF /* NotificationToolbarItem.swift in Sources */,
41174110
B68DE5E32D5A61E5009A43EF /* NotificationManager.swift in Sources */,
41184111
B65B10EC2B073913002852CF /* CEContentUnavailableView.swift in Sources */,
@@ -4435,7 +4428,6 @@
44354428
6C578D8729CD345900DC73B2 /* ExtensionSceneView.swift in Sources */,
44364429
617DB3D02C25AFAE00B58BFE /* TaskNotificationHandler.swift in Sources */,
44374430
77EF6C052C57DE4B00984B69 /* URL+ResouceValues.swift in Sources */,
4438-
B68DE5E72D5A7D62009A43EF /* NotificationBannerEnvironment.swift in Sources */,
44394431
B640A9A129E2188F00715F20 /* View+NavigationBarBackButtonVisible.swift in Sources */,
44404432
587B9E7929301D8F00AC7927 /* GitHubIssueRouter.swift in Sources */,
44414433
B67700F92D2A2662004FD61F /* WorkspacePanelView.swift in Sources */,

CodeEdit/Features/InspectorArea/FileInspector/FileInspectorView.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,33 @@ struct FileInspectorView: View {
5959
widthOptions
6060
wrapLinesToggle
6161
}
62+
Section("Test Notifications") {
63+
Button("Add Test Notification") {
64+
NotificationManager.shared.post(
65+
iconSymbol: "bell.badge.fill",
66+
iconColor: .red,
67+
title: "Test Notification",
68+
description: "This is a test notification",
69+
actionButtonTitle: "Action",
70+
action: {
71+
print("Test notification action triggered")
72+
}
73+
)
74+
}
75+
Button("Add Sticky Notification") {
76+
NotificationManager.shared.post(
77+
iconSymbol: "pin.fill",
78+
iconColor: .orange,
79+
title: "Sticky Notification",
80+
description: "This notification will stay until dismissed",
81+
actionButtonTitle: "Acknowledge",
82+
action: {
83+
print("Sticky notification acknowledged")
84+
},
85+
isSticky: true
86+
)
87+
}
88+
}
6289
}
6390
} else {
6491
NoSelectionInspectorView()

CodeEdit/Features/Notifications/Models/CENotification.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ struct CENotification: Identifiable, Equatable {
1818
let isSticky: Bool
1919
var isRead: Bool
2020
let timestamp: Date
21+
var isBeingDismissed: Bool = false
2122

2223
enum IconType {
2324
case symbol(name: String, color: Color?)

CodeEdit/Features/Notifications/NotificationManager.swift

Lines changed: 110 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,40 @@ final class NotificationManager: NSObject, ObservableObject {
2323
@Published private(set) var notifications: [CENotification] = []
2424

2525
/// Currently displayed notifications in the overlay
26-
@Published private(set) var activeNotification: CENotification?
2726
@Published private(set) var activeNotifications: [CENotification] = []
2827

2928
private var timers: [UUID: Timer] = [:]
3029
private let displayDuration: TimeInterval = 5.0
3130
private var isPaused: Bool = false
3231
private var isAppActive: Bool = true
33-
private var hiddenStickyNotifications: [CENotification] = []
34-
private var hiddenNonStickyNotifications: [CENotification] = []
35-
private var dismissedNotificationIds: Set<UUID> = [] // Track dismissed notifications
32+
33+
/// Whether notifications were manually shown via toolbar
34+
@Published private(set) var isManuallyShown: Bool = false
35+
36+
/// Set of hidden notification IDs
37+
private var hiddenNotificationIds: Set<UUID> = []
38+
39+
/// Whether any non-sticky notifications are currently hidden
40+
private var hasHiddenNotifications: Bool {
41+
activeNotifications.contains { notification in
42+
!notification.isSticky && !isNotificationVisible(notification)
43+
}
44+
}
45+
46+
/// Whether a notification should be visible in the overlay
47+
func isNotificationVisible(_ notification: CENotification) -> Bool {
48+
if notification.isBeingDismissed {
49+
return true // Always show notifications being dismissed
50+
}
51+
if notification.isSticky {
52+
return true // Always show sticky notifications
53+
}
54+
if isManuallyShown {
55+
return true // Show all notifications when manually shown
56+
}
57+
// Otherwise, show if not hidden and has active timer
58+
return !hiddenNotificationIds.contains(notification.id) && timers[notification.id] != nil
59+
}
3660

3761
override private init() {
3862
super.init()
@@ -61,6 +85,16 @@ final class NotificationManager: NSObject, ObservableObject {
6185
@objc
6286
private func applicationDidBecomeActive() {
6387
isAppActive = true
88+
89+
// Show any pending notifications in the overlay
90+
notifications
91+
.filter { notification in
92+
// Only show notifications that aren't already in the overlay
93+
!activeNotifications.contains { $0.id == notification.id }
94+
}
95+
.forEach { notification in
96+
showTemporaryNotification(notification)
97+
}
6498
}
6599

66100
@objc
@@ -103,7 +137,8 @@ final class NotificationManager: NSObject, ObservableObject {
103137
description: description,
104138
actionButtonTitle: actionButtonTitle,
105139
action: action,
106-
isSticky: isSticky
140+
isSticky: isSticky,
141+
isRead: false // Always start as unread
107142
)
108143

109144
DispatchQueue.main.async { [weak self] in
@@ -213,11 +248,37 @@ final class NotificationManager: NSObject, ObservableObject {
213248

214249
/// Shows a notification in the app's overlay UI
215250
private func showTemporaryNotification(_ notification: CENotification) {
216-
activeNotifications.insert(notification, at: 0) // Add to start of array
217-
218-
guard !notification.isSticky else { return }
251+
withAnimation(.easeInOut(duration: 0.3)) {
252+
insertNotification(notification)
253+
hiddenNotificationIds.remove(notification.id) // Ensure new notification is visible
254+
// Only start timer if notifications aren't manually shown
255+
if !isManuallyShown && !notification.isSticky {
256+
startHideTimer(for: notification)
257+
}
258+
}
259+
}
219260

220-
startHideTimer(for: notification)
261+
/// Inserts a notification in the correct position (sticky notifications on top)
262+
private func insertNotification(_ notification: CENotification) {
263+
if notification.isSticky {
264+
// Find the first sticky notification (to insert before it)
265+
if let firstStickyIndex = activeNotifications.firstIndex(where: { $0.isSticky }) {
266+
// Insert at the very start of sticky group
267+
activeNotifications.insert(notification, at: firstStickyIndex)
268+
} else {
269+
// No sticky notifications yet, insert at the start
270+
activeNotifications.insert(notification, at: 0)
271+
}
272+
} else {
273+
// Find the first non-sticky notification
274+
if let firstNonStickyIndex = activeNotifications.firstIndex(where: { !$0.isSticky }) {
275+
// Insert at the start of non-sticky group
276+
activeNotifications.insert(notification, at: firstNonStickyIndex)
277+
} else {
278+
// No non-sticky notifications yet, append at the end
279+
activeNotifications.append(notification)
280+
}
281+
}
221282
}
222283

223284
/// Starts the timer to automatically hide a non-sticky notification
@@ -231,7 +292,14 @@ final class NotificationManager: NSObject, ObservableObject {
231292
withTimeInterval: displayDuration,
232293
repeats: false
233294
) { [weak self] _ in
234-
self?.hideNotification(notification)
295+
guard let self = self else { return }
296+
self.timers[notification.id] = nil
297+
298+
withAnimation(.easeInOut(duration: 0.3)) {
299+
// Hide this specific notification
300+
self.hiddenNotificationIds.insert(notification.id)
301+
self.objectWillChange.send()
302+
}
235303
}
236304
}
237305

@@ -244,23 +312,29 @@ final class NotificationManager: NSObject, ObservableObject {
244312
/// Resumes all auto-hide timers
245313
func resumeTimer() {
246314
isPaused = false
315+
// Only restart timers for notifications that are currently visible
247316
activeNotifications
248-
.filter { !$0.isSticky }
317+
.filter { !$0.isSticky && isNotificationVisible($0) }
249318
.forEach { startHideTimer(for: $0) }
250319
}
251320

252-
/// Hides a specific notification
253-
private func hideNotification(_ notification: CENotification) {
254-
timers[notification.id]?.invalidate()
255-
timers[notification.id] = nil
256-
activeNotifications.removeAll(where: { $0.id == notification.id })
257-
}
258-
259321
/// Dismisses a specific notification
260322
func dismissNotification(_ notification: CENotification) {
261-
hideNotification(notification)
262-
dismissedNotificationIds.insert(notification.id) // Track dismissed notification
323+
timers[notification.id]?.invalidate()
324+
timers[notification.id] = nil
325+
hiddenNotificationIds.remove(notification.id)
326+
327+
if let index = activeNotifications.firstIndex(where: { $0.id == notification.id }) {
328+
activeNotifications[index].isBeingDismissed = true
329+
}
330+
331+
withAnimation(.easeOut(duration: 0.2)) {
332+
activeNotifications.removeAll(where: { $0.id == notification.id })
333+
}
263334
notifications.removeAll(where: { $0.id == notification.id })
335+
336+
// Mark as read when dismissed
337+
markAsRead(notification)
264338
}
265339

266340
/// Marks a notification as read
@@ -281,21 +355,22 @@ final class NotificationManager: NSObject, ObservableObject {
281355
}
282356
}
283357

284-
/// Hides all notifications from the overlay view
285-
func hideOverlayNotifications() {
286-
dismissedNotificationIds.removeAll() // Clear dismissed tracking when hiding
287-
hiddenStickyNotifications = activeNotifications.filter { $0.isSticky }
288-
hiddenNonStickyNotifications = activeNotifications.filter { !$0.isSticky }
289-
activeNotifications.removeAll()
290-
}
291-
292-
/// Restores only sticky notifications to the overlay
293-
func restoreOverlayStickies() {
294-
// Only restore sticky notifications that weren't dismissed
295-
let nonDismissedStickies = hiddenStickyNotifications.filter { !dismissedNotificationIds.contains($0.id) }
296-
activeNotifications.insert(contentsOf: nonDismissedStickies, at: 0)
297-
hiddenStickyNotifications.removeAll()
298-
dismissedNotificationIds.removeAll() // Clear tracking after restore
358+
/// Toggles visibility of notifications in the overlay
359+
func toggleNotificationsVisibility() {
360+
withAnimation(.easeInOut(duration: 0.3)) {
361+
if hasHiddenNotifications || !isManuallyShown {
362+
// Show all notifications
363+
isManuallyShown = true
364+
hiddenNotificationIds.removeAll() // Clear all hidden states
365+
} else {
366+
// Hide all non-sticky notifications
367+
isManuallyShown = false
368+
activeNotifications
369+
.filter { !$0.isSticky }
370+
.forEach { hiddenNotificationIds.insert($0.id) }
371+
}
372+
objectWillChange.send()
373+
}
299374
}
300375
}
301376

CodeEdit/Features/Notifications/Views/NotificationBannerEnvironment.swift

Lines changed: 0 additions & 28 deletions
This file was deleted.

0 commit comments

Comments
 (0)