Skip to content

Commit 779e3a6

Browse files
committed
Refactored FeatureIcon and notifications to support custom images
1 parent 5aac83d commit 779e3a6

15 files changed

+264
-72
lines changed

CodeEdit/AppDelegate.swift

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

2727
NSApp.closeWindow(.welcome, .about)
28-
28+
2929
// Add test notification
3030
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
3131
NotificationManager.shared.post(
32-
icon: "bell.badge",
32+
iconSymbol: "bell.badge",
3333
title: "Welcome to CodeEdit",
3434
description: "This is a test notification to demonstrate the notification system.",
3535
actionButtonTitle: "Learn More",
@@ -38,10 +38,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
3838
}
3939
)
4040
}
41-
41+
4242
DispatchQueue.main.async {
4343
var needToHandleOpen = true
44-
44+
4545
// If no windows were reopened by NSQuitAlwaysKeepsWindows, do default behavior.
4646
// Non-WindowGroup SwiftUI Windows are still in NSApp.windows when they are closed,
4747
// So we need to think about those.
@@ -73,13 +73,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
7373
}
7474

7575
func applicationWillTerminate(_ aNotification: Notification) {
76-
76+
7777
}
78-
78+
7979
func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
8080
true
8181
}
82-
82+
8383
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
8484
guard flag else {
8585
handleOpen()
@@ -92,11 +92,11 @@ final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
9292
sender.windows.first(where: { $0.isMiniaturized })?.deminiaturize(sender)
9393
return false
9494
}
95-
95+
9696
func applicationShouldOpenUntitledFile(_ sender: NSApplication) -> Bool {
9797
false
9898
}
99-
99+
100100
func handleOpen() {
101101
let behavior = Settings.shared.preferences.general.reopenBehavior
102102
switch behavior {
@@ -110,15 +110,15 @@ final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
110110
CodeEditDocumentController.shared.newDocument(self)
111111
}
112112
}
113-
113+
114114
/// Handle urls with the form `codeedit://file/{filepath}:{line}:{column}`
115115
func application(_ application: NSApplication, open urls: [URL]) {
116116
for url in urls {
117117
let file = URL(fileURLWithPath: url.path).path.split(separator: ":")
118118
let filePath = URL(fileURLWithPath: String(file[0]))
119119
let line = file.count > 1 ? Int(file[1]) ?? 0 : 0
120120
let column = file.count > 2 ? Int(file[2]) ?? 1 : 1
121-
121+
122122
CodeEditDocumentController.shared
123123
.openDocument(withContentsOf: filePath, display: true) { document, _, error in
124124
if let error {
@@ -133,7 +133,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
133133
// Add notification when workspace is opened via URL
134134
if let workspaceDoc = document as? WorkspaceDocument {
135135
NotificationManager.shared.post(
136-
icon: "folder.badge.plus",
136+
iconSymbol: "folder.badge.plus",
137137
title: "Workspace Opened",
138138
description: "Successfully opened workspace: \(workspaceDoc.fileURL?.lastPathComponent ?? "")",
139139
actionButtonTitle: "View Files",
@@ -146,7 +146,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
146146
}
147147
}
148148
}
149-
149+
150150
/// Defers the application terminate message until we've finished cleanup.
151151
///
152152
/// All paths _must_ call `NSApplication.shared.reply(toApplicationShouldTerminate: true)` as soon as possible.

CodeEdit/Features/CodeEditUI/Views/FeatureIcon.swift

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,39 +6,77 @@
66
//
77

88
import SwiftUI
9+
import CodeEditSymbols
910

1011
struct FeatureIcon: View {
11-
private let symbol: Image
12-
private let color: Color
12+
private let content: IconContent
13+
private let color: Color?
1314
private let size: CGFloat
1415

1516
init(
16-
symbol: Image?,
17-
color: Color?,
18-
size: CGFloat?
17+
symbol: String,
18+
color: Color? = nil,
19+
size: CGFloat? = nil
1920
) {
20-
self.symbol = symbol ?? Image(systemName: "exclamationmark.triangle")
21-
self.color = color ?? .white
21+
self.content = .symbol(symbol)
22+
self.color = color ?? .accentColor
2223
self.size = size ?? 20
2324
}
2425

25-
var body: some View {
26-
Group {
27-
symbol
28-
.resizable()
29-
.aspectRatio(contentMode: .fit)
26+
init(
27+
image: Image,
28+
size: CGFloat? = nil
29+
) {
30+
self.content = .image(image)
31+
self.color = nil
32+
self.size = size ?? 20
33+
}
34+
35+
private func getSafeImage(named: String) -> Image {
36+
if NSImage(systemSymbolName: named, accessibilityDescription: nil) != nil {
37+
return Image(systemName: named)
38+
} else {
39+
return Image(symbol: named)
3040
}
31-
.shadow(color: Color(NSColor.black).opacity(0.25), radius: size / 40, y: size / 40)
32-
.padding(size / 8)
33-
.foregroundColor(.white)
34-
.frame(width: size, height: size)
35-
.background(
36-
RoundedRectangle(
37-
cornerRadius: size / 4,
38-
style: .continuous
41+
}
42+
43+
var body: some View {
44+
RoundedRectangle(cornerRadius: size / 4, style: .continuous)
45+
.fill(background)
46+
.overlay {
47+
switch content {
48+
case .symbol(let name):
49+
getSafeImage(named: name)
50+
.resizable()
51+
.aspectRatio(contentMode: .fit)
52+
.foregroundColor(.white)
53+
.padding(size / 8)
54+
case .image(let image):
55+
image
56+
.resizable()
57+
.aspectRatio(contentMode: .fill)
58+
}
59+
}
60+
.clipShape(RoundedRectangle(cornerRadius: size / 4, style: .continuous))
61+
.shadow(
62+
color: Color(NSColor.black).opacity(0.25),
63+
radius: size / 40,
64+
y: size / 40
3965
)
40-
.fill(color.gradient)
41-
.shadow(color: Color(NSColor.black).opacity(0.25), radius: size / 40, y: size / 40)
42-
)
66+
.frame(width: size, height: size)
4367
}
68+
69+
private var background: AnyShapeStyle {
70+
switch content {
71+
case .symbol:
72+
return AnyShapeStyle((color ?? .accentColor).gradient)
73+
case .image:
74+
return AnyShapeStyle(.regularMaterial)
75+
}
76+
}
77+
}
78+
79+
private enum IconContent {
80+
case symbol(String)
81+
case image(Image)
4482
}

CodeEdit/Features/InspectorArea/FileInspector/FileInspectorView.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ struct FileInspectorView: View {
9191

9292
func addTestNotification () {
9393
NotificationManager.shared.post(
94-
icon: "bell",
94+
iconSymbol: "bell",
95+
iconColor: .red,
9596
title: "New Notification Created",
9697
description: "Successfully created new notification",
9798
actionButtonTitle: "Action",
@@ -100,7 +101,7 @@ struct FileInspectorView: View {
100101
}
101102
)
102103
}
103-
104+
104105
func addTestNotificationAfterDelay () {
105106
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
106107
addTestNotification()

CodeEdit/Features/Notifications/Models/CENotification.swift

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1+
//
2+
// CENotification.swift
3+
// CodeEdit
4+
//
5+
// Created by Austin Condiff on 2/10/24.
6+
//
7+
8+
import Foundation
19
import SwiftUI
210

311
struct CENotification: Identifiable, Equatable {
412
let id: UUID
5-
let icon: String // SF Symbol name
13+
let icon: IconType
614
let title: String
715
let description: String
816
let actionButtonTitle: String
@@ -11,9 +19,36 @@ struct CENotification: Identifiable, Equatable {
1119
var isRead: Bool
1220
let timestamp: Date
1321

22+
enum IconType {
23+
case symbol(name: String, color: Color?)
24+
case image(Image)
25+
}
26+
27+
init(
28+
id: UUID = UUID(),
29+
iconSymbol: String,
30+
iconColor: Color? = nil,
31+
title: String,
32+
description: String,
33+
actionButtonTitle: String,
34+
action: @escaping () -> Void,
35+
isSticky: Bool = false,
36+
isRead: Bool = false
37+
) {
38+
self.id = id
39+
self.icon = .symbol(name: iconSymbol, color: iconColor)
40+
self.title = title
41+
self.description = description
42+
self.actionButtonTitle = actionButtonTitle
43+
self.action = action
44+
self.isSticky = isSticky
45+
self.isRead = isRead
46+
self.timestamp = Date()
47+
}
48+
1449
init(
1550
id: UUID = UUID(),
16-
icon: String,
51+
iconImage: Image,
1752
title: String,
1853
description: String,
1954
actionButtonTitle: String,
@@ -22,7 +57,7 @@ struct CENotification: Identifiable, Equatable {
2257
isRead: Bool = false
2358
) {
2459
self.id = id
25-
self.icon = icon
60+
self.icon = .image(iconImage)
2661
self.title = title
2762
self.description = description
2863
self.actionButtonTitle = actionButtonTitle

0 commit comments

Comments
 (0)