Skip to content

Commit 5aac83d

Browse files
committed
Added global notifications system
1 parent 0e12166 commit 5aac83d

17 files changed

+651
-30
lines changed

CodeEdit.xcodeproj/project.pbxproj

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,13 @@
580580
B67DBB942CD5FC08007F4F18 /* GlobPatternListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67DBB932CD5FBE2007F4F18 /* GlobPatternListItem.swift */; };
581581
B68108042C60287F008B27C1 /* StartTaskToolbarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68108032C60287F008B27C1 /* StartTaskToolbarButton.swift */; };
582582
B685DE7929CC9CCD002860C8 /* StatusBarIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = B685DE7829CC9CCD002860C8 /* StatusBarIcon.swift */; };
583+
B68DE5DF2D5A61E5009A43EF /* CENotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68DE5D72D5A61E5009A43EF /* CENotification.swift */; };
584+
B68DE5E02D5A61E5009A43EF /* NotificationBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68DE5D92D5A61E5009A43EF /* NotificationBannerView.swift */; };
585+
B68DE5E12D5A61E5009A43EF /* NotificationListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68DE5DA2D5A61E5009A43EF /* NotificationListView.swift */; };
586+
B68DE5E22D5A61E5009A43EF /* NotificationToolbarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68DE5DB2D5A61E5009A43EF /* NotificationToolbarItem.swift */; };
587+
B68DE5E32D5A61E5009A43EF /* NotificationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68DE5DD2D5A61E5009A43EF /* NotificationManager.swift */; };
588+
B68DE5E52D5A7988009A43EF /* NotificationOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68DE5E42D5A7988009A43EF /* NotificationOverlayView.swift */; };
589+
B68DE5E72D5A7D62009A43EF /* NotificationBannerEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68DE5E62D5A7D62009A43EF /* NotificationBannerEnvironment.swift */; };
583590
B6966A282C2F683300259C2D /* SourceControlPullView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6966A272C2F683300259C2D /* SourceControlPullView.swift */; };
584591
B6966A2A2C2F687A00259C2D /* SourceControlFetchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6966A292C2F687A00259C2D /* SourceControlFetchView.swift */; };
585592
B6966A2E2C3056AD00259C2D /* SourceControlCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6966A2D2C3056AD00259C2D /* SourceControlCommands.swift */; };
@@ -1270,6 +1277,13 @@
12701277
B67DBB932CD5FBE2007F4F18 /* GlobPatternListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobPatternListItem.swift; sourceTree = "<group>"; };
12711278
B68108032C60287F008B27C1 /* StartTaskToolbarButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartTaskToolbarButton.swift; sourceTree = "<group>"; };
12721279
B685DE7829CC9CCD002860C8 /* StatusBarIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusBarIcon.swift; sourceTree = "<group>"; };
1280+
B68DE5D72D5A61E5009A43EF /* CENotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CENotification.swift; sourceTree = "<group>"; };
1281+
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>"; };
1283+
B68DE5DB2D5A61E5009A43EF /* NotificationToolbarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationToolbarItem.swift; sourceTree = "<group>"; };
1284+
B68DE5DD2D5A61E5009A43EF /* NotificationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationManager.swift; sourceTree = "<group>"; };
1285+
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>"; };
12731287
B6966A272C2F683300259C2D /* SourceControlPullView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlPullView.swift; sourceTree = "<group>"; };
12741288
B6966A292C2F687A00259C2D /* SourceControlFetchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlFetchView.swift; sourceTree = "<group>"; };
12751289
B6966A2D2C3056AD00259C2D /* SourceControlCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceControlCommands.swift; sourceTree = "<group>"; };
@@ -1761,6 +1775,7 @@
17611775
58A5DF9D29339F6400D1BD5D /* Keybindings */,
17621776
30B087FB2C0D53080063A882 /* LSP */,
17631777
287776EA27E350A100D46668 /* NavigatorArea */,
1778+
B68DE5DE2D5A61E5009A43EF /* Notifications */,
17641779
5878DAA0291AE76700DD95A3 /* OpenQuickly */,
17651780
58798210292D92370085B254 /* Search */,
17661781
B67B270029D7868000FB9301 /* Settings */,
@@ -2130,6 +2145,7 @@
21302145
587B9D8F29300ABD00AC7927 /* ToolbarBranchPicker.swift */,
21312146
B6DCDAC52CCDE2B90099FBF9 /* InstantPopoverModifier.swift */,
21322147
2897E1C62979A29200741E32 /* TrackableScrollView.swift */,
2148+
B696A7E52CFE20C40048CFE1 /* FeatureIcon.swift */,
21332149
B60718302B15A9A3009CDAB4 /* CEOutlineGroup.swift */,
21342150
);
21352151
path = Views;
@@ -3547,6 +3563,36 @@
35473563
path = Settings;
35483564
sourceTree = "<group>";
35493565
};
3566+
B68DE5D82D5A61E5009A43EF /* Models */ = {
3567+
isa = PBXGroup;
3568+
children = (
3569+
B68DE5D72D5A61E5009A43EF /* CENotification.swift */,
3570+
);
3571+
path = Models;
3572+
sourceTree = "<group>";
3573+
};
3574+
B68DE5DC2D5A61E5009A43EF /* Views */ = {
3575+
isa = PBXGroup;
3576+
children = (
3577+
B68DE5E62D5A7D62009A43EF /* NotificationBannerEnvironment.swift */,
3578+
B68DE5D92D5A61E5009A43EF /* NotificationBannerView.swift */,
3579+
B68DE5DA2D5A61E5009A43EF /* NotificationListView.swift */,
3580+
B68DE5DB2D5A61E5009A43EF /* NotificationToolbarItem.swift */,
3581+
B68DE5E42D5A7988009A43EF /* NotificationOverlayView.swift */,
3582+
);
3583+
path = Views;
3584+
sourceTree = "<group>";
3585+
};
3586+
B68DE5DE2D5A61E5009A43EF /* Notifications */ = {
3587+
isa = PBXGroup;
3588+
children = (
3589+
B68DE5D82D5A61E5009A43EF /* Models */,
3590+
B68DE5DC2D5A61E5009A43EF /* Views */,
3591+
B68DE5DD2D5A61E5009A43EF /* NotificationManager.swift */,
3592+
);
3593+
path = Notifications;
3594+
sourceTree = "<group>";
3595+
};
35503596
B6966A262C2F673A00259C2D /* Views */ = {
35513597
isa = PBXGroup;
35523598
children = (
@@ -3643,7 +3689,6 @@
36433689
B6CF632A29E5436C0085880A /* Views */ = {
36443690
isa = PBXGroup;
36453691
children = (
3646-
B696A7E52CFE20C40048CFE1 /* FeatureIcon.swift */,
36473692
B67DBB932CD5FBE2007F4F18 /* GlobPatternListItem.swift */,
36483693
B67DBB912CD5EAA4007F4F18 /* GlobPatternList.swift */,
36493694
B6041F4C29D7A4E9000F3454 /* SettingsPageView.swift */,
@@ -4065,6 +4110,11 @@
40654110
B6BF41422C2C672A003AB4B3 /* SourceControlPushView.swift in Sources */,
40664111
587B9E8429301D8F00AC7927 /* GitHubUser.swift in Sources */,
40674112
04BA7C1C2AE2D84100584E1C /* GitClient+Commit.swift in Sources */,
4113+
B68DE5DF2D5A61E5009A43EF /* CENotification.swift in Sources */,
4114+
B68DE5E02D5A61E5009A43EF /* NotificationBannerView.swift in Sources */,
4115+
B68DE5E12D5A61E5009A43EF /* NotificationListView.swift in Sources */,
4116+
B68DE5E22D5A61E5009A43EF /* NotificationToolbarItem.swift in Sources */,
4117+
B68DE5E32D5A61E5009A43EF /* NotificationManager.swift in Sources */,
40684118
B65B10EC2B073913002852CF /* CEContentUnavailableView.swift in Sources */,
40694119
5B698A0A2B262FA000DE9392 /* SearchSettingsView.swift in Sources */,
40704120
B65B10FB2B08B054002852CF /* Divided.swift in Sources */,
@@ -4183,6 +4233,7 @@
41834233
B6B2D7A12CE8797B00379967 /* GitConfigExtensions.swift in Sources */,
41844234
587B9E7329301D8F00AC7927 /* GitRouter.swift in Sources */,
41854235
6C2C156129B4F52F00EA60A5 /* SplitViewModifiers.swift in Sources */,
4236+
B68DE5E52D5A7988009A43EF /* NotificationOverlayView.swift in Sources */,
41864237
61A53A812B4449F00093BF8A /* WorkspaceDocument+Index.swift in Sources */,
41874238
66AF6CE22BF17CC300D83C9D /* StatusBarViewModel.swift in Sources */,
41884239
30CB648D2C12680F00CC8A9E /* LSPService+Events.swift in Sources */,
@@ -4384,6 +4435,7 @@
43844435
6C578D8729CD345900DC73B2 /* ExtensionSceneView.swift in Sources */,
43854436
617DB3D02C25AFAE00B58BFE /* TaskNotificationHandler.swift in Sources */,
43864437
77EF6C052C57DE4B00984B69 /* URL+ResouceValues.swift in Sources */,
4438+
B68DE5E72D5A7D62009A43EF /* NotificationBannerEnvironment.swift in Sources */,
43874439
B640A9A129E2188F00715F20 /* View+NavigationBarBackButtonVisible.swift in Sources */,
43884440
587B9E7929301D8F00AC7927 /* GitHubIssueRouter.swift in Sources */,
43894441
B67700F92D2A2662004FD61F /* WorkspacePanelView.swift in Sources */,

CodeEdit/AppDelegate.swift

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,23 @@ final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
2525
checkForFilesToOpen()
2626

2727
NSApp.closeWindow(.welcome, .about)
28-
28+
29+
// Add test notification
30+
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
31+
NotificationManager.shared.post(
32+
icon: "bell.badge",
33+
title: "Welcome to CodeEdit",
34+
description: "This is a test notification to demonstrate the notification system.",
35+
actionButtonTitle: "Learn More",
36+
action: {
37+
print("Action button clicked!")
38+
}
39+
)
40+
}
41+
2942
DispatchQueue.main.async {
3043
var needToHandleOpen = true
31-
44+
3245
// If no windows were reopened by NSQuitAlwaysKeepsWindows, do default behavior.
3346
// Non-WindowGroup SwiftUI Windows are still in NSApp.windows when they are closed,
3447
// So we need to think about those.
@@ -60,30 +73,30 @@ final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
6073
}
6174

6275
func applicationWillTerminate(_ aNotification: Notification) {
63-
76+
6477
}
65-
78+
6679
func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
6780
true
6881
}
69-
82+
7083
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
7184
guard flag else {
7285
handleOpen()
7386
return false
7487
}
75-
88+
7689
/// Check if all windows are either miniaturized or not visible.
7790
/// If so, attempt to find the first miniaturized window and deminiaturize it.
7891
guard sender.windows.allSatisfy({ $0.isMiniaturized || !$0.isVisible }) else { return false }
7992
sender.windows.first(where: { $0.isMiniaturized })?.deminiaturize(sender)
8093
return false
8194
}
82-
95+
8396
func applicationShouldOpenUntitledFile(_ sender: NSApplication) -> Bool {
8497
false
8598
}
86-
99+
87100
func handleOpen() {
88101
let behavior = Settings.shared.preferences.general.reopenBehavior
89102
switch behavior {
@@ -97,15 +110,15 @@ final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
97110
CodeEditDocumentController.shared.newDocument(self)
98111
}
99112
}
100-
113+
101114
/// Handle urls with the form `codeedit://file/{filepath}:{line}:{column}`
102115
func application(_ application: NSApplication, open urls: [URL]) {
103116
for url in urls {
104117
let file = URL(fileURLWithPath: url.path).path.split(separator: ":")
105118
let filePath = URL(fileURLWithPath: String(file[0]))
106119
let line = file.count > 1 ? Int(file[1]) ?? 0 : 0
107120
let column = file.count > 2 ? Int(file[2]) ?? 1 : 1
108-
121+
109122
CodeEditDocumentController.shared
110123
.openDocument(withContentsOf: filePath, display: true) { document, _, error in
111124
if let error {
@@ -117,10 +130,23 @@ final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
117130
cursorPositions: [CursorPosition(line: line, column: column > 0 ? column : 1)]
118131
)
119132
}
133+
// Add notification when workspace is opened via URL
134+
if let workspaceDoc = document as? WorkspaceDocument {
135+
NotificationManager.shared.post(
136+
icon: "folder.badge.plus",
137+
title: "Workspace Opened",
138+
description: "Successfully opened workspace: \(workspaceDoc.fileURL?.lastPathComponent ?? "")",
139+
actionButtonTitle: "View Files",
140+
action: {
141+
// Ensure the workspace window is frontmost
142+
workspaceDoc.windowControllers.first?.window?.makeKeyAndOrderFront(nil)
143+
}
144+
)
145+
}
120146
}
121147
}
122148
}
123-
149+
124150
/// Defers the application terminate message until we've finished cleanup.
125151
///
126152
/// All paths _must_ call `NSApplication.shared.reply(toApplicationShouldTerminate: true)` as soon as possible.
@@ -138,9 +164,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
138164
func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
139165
let projects: [String] = CodeEditDocumentController.shared.documents
140166
.compactMap { ($0 as? WorkspaceDocument)?.fileURL?.path }
141-
167+
142168
UserDefaults.standard.set(projects, forKey: AppDelegate.recoverWorkspacesKey)
143-
169+
144170
let areAllDocumentsClean = CodeEditDocumentController.shared.documents.allSatisfy { !$0.isDocumentEdited }
145171
guard areAllDocumentsClean else {
146172
CodeEditDocumentController.shared.closeAllDocuments(

CodeEdit/Features/About/Views/BlurButtonStyle.swift

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,18 @@ import SwiftUI
99

1010
extension ButtonStyle where Self == BlurButtonStyle {
1111
static var blur: BlurButtonStyle { BlurButtonStyle() }
12+
static var secondaryBlur: BlurButtonStyle { BlurButtonStyle(isSecondary: true) }
1213
}
1314

1415
struct BlurButtonStyle: ButtonStyle {
16+
var isSecondary: Bool = false
17+
1518
@Environment(\.controlSize)
1619
var controlSize
1720

21+
@Environment(\.colorScheme)
22+
var colorScheme
23+
1824
var height: CGFloat {
1925
switch controlSize {
2026
case .large:
@@ -24,32 +30,39 @@ struct BlurButtonStyle: ButtonStyle {
2430
}
2531
}
2632

27-
@Environment(\.colorScheme)
28-
var colorScheme
29-
3033
func makeBody(configuration: Configuration) -> some View {
3134
configuration.label
35+
.padding(.horizontal, 8)
3236
.frame(height: height)
33-
.buttonStyle(.bordered)
3437
.background {
3538
switch colorScheme {
3639
case .dark:
37-
Color
38-
.gray
39-
.opacity(0.001)
40-
.overlay(.regularMaterial.blendMode(.plusLighter))
41-
.overlay(Color.gray.opacity(0.30))
42-
.overlay(Color.white.opacity(configuration.isPressed ? 0.20 : 0.00))
40+
ZStack {
41+
Color.gray.opacity(0.001)
42+
if !isSecondary {
43+
Rectangle()
44+
.fill(.regularMaterial)
45+
.blendMode(.plusLighter)
46+
}
47+
Color.gray.opacity(isSecondary ? 0.10 : 0.30)
48+
Color.white.opacity(configuration.isPressed ? 0.10 : 0.00)
49+
}
4350
case .light:
44-
Color
45-
.gray
46-
.opacity(0.001)
47-
.overlay(.regularMaterial.blendMode(.darken))
48-
.overlay(Color.gray.opacity(0.15).blendMode(.plusDarker))
51+
ZStack {
52+
Color.gray.opacity(0.001)
53+
if !isSecondary {
54+
Rectangle()
55+
.fill(.regularMaterial)
56+
.blendMode(.darken)
57+
}
58+
Color.gray.opacity(isSecondary ? 0.05 : 0.15)
59+
.blendMode(.plusDarker)
60+
Color.gray.opacity(configuration.isPressed ? 0.10 : 0.00)
61+
}
4962
@unknown default:
5063
Color.black
5164
}
5265
}
53-
.clipShape(RoundedRectangle(cornerRadius: 6))
66+
.clipShape(RoundedRectangle(cornerRadius: controlSize == .large ? 6 : 5))
5467
}
5568
}

CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ extension CodeEditWindowController {
3131
.branchPicker,
3232
.flexibleSpace,
3333
.activityViewer,
34+
.notificationItem,
3435
.flexibleSpace,
3536
.itemListTrackingSeparator,
3637
.flexibleSpace,
@@ -47,6 +48,7 @@ extension CodeEditWindowController {
4748
.toggleLastSidebarItem,
4849
.branchPicker,
4950
.activityViewer,
51+
.notificationItem,
5052
.startTaskSidebarItem,
5153
.stopTaskSidebarItem
5254
]
@@ -173,6 +175,11 @@ extension CodeEditWindowController {
173175
strongWidth
174176
])
175177

178+
toolbarItem.view = view
179+
return toolbarItem
180+
case .notificationItem:
181+
let toolbarItem = NSToolbarItem(itemIdentifier: .notificationItem)
182+
let view = NSHostingView(rootView: NotificationToolbarItem())
176183
toolbarItem.view = view
177184
return toolbarItem
178185
default:

CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,4 +140,5 @@ extension NSToolbarItem.Identifier {
140140
static let itemListTrackingSeparator = NSToolbarItem.Identifier("ItemListTrackingSeparator")
141141
static let branchPicker: NSToolbarItem.Identifier = NSToolbarItem.Identifier("BranchPicker")
142142
static let activityViewer: NSToolbarItem.Identifier = NSToolbarItem.Identifier("ActivityViewer")
143+
static let notificationItem = NSToolbarItem.Identifier("notificationItem")
143144
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

CodeEdit/Features/InspectorArea/FileInspector/FileInspectorView.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,14 @@ struct FileInspectorView: View {
5959
widthOptions
6060
wrapLinesToggle
6161
}
62+
Section {
63+
Button("Add Test Notification") {
64+
addTestNotification()
65+
}
66+
Button("Add Test Notification After Delay") {
67+
addTestNotificationAfterDelay()
68+
}
69+
}
6270
}
6371
} else {
6472
NoSelectionInspectorView()
@@ -81,6 +89,24 @@ struct FileInspectorView: View {
8189
}
8290
}
8391

92+
func addTestNotification () {
93+
NotificationManager.shared.post(
94+
icon: "bell",
95+
title: "New Notification Created",
96+
description: "Successfully created new notification",
97+
actionButtonTitle: "Action",
98+
action: {
99+
print("Action taken")
100+
}
101+
)
102+
}
103+
104+
func addTestNotificationAfterDelay () {
105+
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
106+
addTestNotification()
107+
}
108+
}
109+
84110
@ViewBuilder private var fileNameField: some View {
85111
if let file {
86112
TextField("Name", text: $fileName)

0 commit comments

Comments
 (0)