diff --git a/ClassDumper.xcodeproj/project.pbxproj b/ClassDumper.xcodeproj/project.pbxproj index c8a6758..0a6170d 100644 --- a/ClassDumper.xcodeproj/project.pbxproj +++ b/ClassDumper.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ 3A5863712B84564F00487B13 /* URL.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A5863702B84564F00487B13 /* URL.swift */; }; 3A5863F02B8C069C00487B13 /* FilterScopeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A5863EF2B8C069C00487B13 /* FilterScopeView.swift */; }; 3A5863F22B8C14A400487B13 /* FilePathView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A5863F12B8C14A400487B13 /* FilePathView.swift */; }; + 3A67A98E2B94F0A700961FF9 /* CodableColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A67A98D2B94F0A700961FF9 /* CodableColor.swift */; }; 3A67D7642A511F7600516FF2 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A67D7632A511F7600516FF2 /* App.swift */; }; 3A67D7662A511FFA00516FF2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A67D7652A511FFA00516FF2 /* AppDelegate.swift */; }; 3A67D7682A51566B00516FF2 /* String+ConsoleOutput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A67D7672A51566B00516FF2 /* String+ConsoleOutput.swift */; }; @@ -95,6 +96,7 @@ 3A5863702B84564F00487B13 /* URL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URL.swift; sourceTree = ""; }; 3A5863EF2B8C069C00487B13 /* FilterScopeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterScopeView.swift; sourceTree = ""; }; 3A5863F12B8C14A400487B13 /* FilePathView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePathView.swift; sourceTree = ""; }; + 3A67A98D2B94F0A700961FF9 /* CodableColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodableColor.swift; sourceTree = ""; }; 3A67D7632A511F7600516FF2 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; 3A67D7652A511FFA00516FF2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 3A67D7672A51566B00516FF2 /* String+ConsoleOutput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+ConsoleOutput.swift"; sourceTree = ""; }; @@ -282,6 +284,7 @@ 3A91CDC22A577B0E00C85A27 /* Binding.swift */, 3A827B002A342ABF006A1A96 /* Endpoint.swift */, 3A5863702B84564F00487B13 /* URL.swift */, + 3A67A98D2B94F0A700961FF9 /* CodableColor.swift */, ); path = Extension; sourceTree = ""; @@ -480,6 +483,7 @@ 3AA565472A25BD4900EBD7FB /* Notification.swift in Sources */, 3AA564FF2A232B4C00EBD7FB /* SettingsView.swift in Sources */, 3A67D7642A511F7600516FF2 /* App.swift in Sources */, + 3A67A98E2B94F0A700961FF9 /* CodableColor.swift in Sources */, 3A2F8F752A397CF100D9FB26 /* DatabaseButtons.swift in Sources */, 3A91CDDA2A57BEDE00C85A27 /* Keys.swift in Sources */, 3A67D7682A51566B00516FF2 /* String+ConsoleOutput.swift in Sources */, diff --git a/ClassDumper/AppView.swift b/ClassDumper/AppView.swift index 0cdc9ed..044a8cc 100644 --- a/ClassDumper/AppView.swift +++ b/ClassDumper/AppView.swift @@ -7,6 +7,7 @@ typealias FileDatabase = Array struct AppView: View { @Environment(\.fileRepository) private var fileRepository + @AppStorage("accent") var accent = CodableColor(.accentColor) @Query(FileCountRequest()) fileprivate var fileCount: Int @@ -44,6 +45,7 @@ struct AppView: View { parseDirectory() } .navigationTitle(NSApplication.bundleName) + .tint(accent.toColor()) } } @@ -58,7 +60,7 @@ extension AppView { } }, label: { Label("Edit files", systemImage: "pencil") - .foregroundColor(deletionEnabled ? .accentColor : .none) + .foregroundColor(deletionEnabled ? accent.toColor() : .none) }) .keyboardShortcut("e", modifiers: [.command]) } diff --git a/ClassDumper/ClassDumperApp.swift b/ClassDumper/ClassDumperApp.swift index fa73e64..a807df5 100644 --- a/ClassDumper/ClassDumperApp.swift +++ b/ClassDumper/ClassDumperApp.swift @@ -5,7 +5,8 @@ import SwiftUI struct ClassDumperApp: App { @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate @StateObject private var alertController = AlertController() - + @AppStorage("accent") var accent = CodableColor(.accentColor) + var body: some Scene { WindowGroup { AppView() diff --git a/ClassDumper/Extension/CodableColor.swift b/ClassDumper/Extension/CodableColor.swift new file mode 100644 index 0000000..bb1426f --- /dev/null +++ b/ClassDumper/Extension/CodableColor.swift @@ -0,0 +1,45 @@ +import SwiftUI + +struct AccentColor: Identifiable { + var id: String { name } + var color: CodableColor + var name: String +} + +var accents: [AccentColor] = [ + .init(color: CodableColor(.blue), name: "Blue"), + .init(color: CodableColor(.purple), name: "Purple"), + .init(color: CodableColor(.pink), name: "Pink"), + .init(color: CodableColor(.red), name: "Red"), + .init(color: CodableColor(.orange), name: "Orange"), + .init(color: CodableColor(.yellow), name: "Yellow"), + .init(color: CodableColor(.green), name: "Green"), + .init(color: CodableColor(.gray), name: "Gray"), +] + +struct CodableColor: RawRepresentable, Codable { + let rawValue: String + + init(rawValue: String) { + self.rawValue = rawValue + } + + init(_ color: Color) { + let nsColor = NSColor(color).usingColorSpace(.deviceRGB)! + let red = Int(nsColor.redComponent * 255) + let green = Int(nsColor.greenComponent * 255) + let blue = Int(nsColor.blueComponent * 255) + let alpha = Int(nsColor.alphaComponent * 255) + + self.rawValue = String(format: "%02X%02X%02X%02X", red, green, blue, alpha) + } + + func toColor() -> Color { + let red = Double(Int(rawValue.prefix(2), radix: 16)!) / 255.0 + let green = Double(Int(rawValue.dropFirst(2).prefix(2), radix: 16)!) / 255.0 + let blue = Double(Int(rawValue.dropFirst(4).prefix(2), radix: 16)!) / 255.0 + let alpha = Double(Int(rawValue.dropFirst(6).prefix(2), radix: 16)!) / 255.0 + + return Color(NSColor(calibratedRed: CGFloat(red), green: CGFloat(green), blue: CGFloat(blue), alpha: CGFloat(alpha))) + } +} diff --git a/ClassDumper/Shared/Keys.swift b/ClassDumper/Shared/Keys.swift index 6c62aa8..48c9d72 100644 --- a/ClassDumper/Shared/Keys.swift +++ b/ClassDumper/Shared/Keys.swift @@ -17,6 +17,10 @@ struct Keys { static let PathBarFile = "PathBarFile" } + struct Settings { + static let AccentColorButton = "AccentColorButton" + } + struct Filters { static let FilterFiles = "FilterFiles" } diff --git a/ClassDumper/Views/FilePathView.swift b/ClassDumper/Views/FilePathView.swift index 8c1b195..313dbe0 100644 --- a/ClassDumper/Views/FilePathView.swift +++ b/ClassDumper/Views/FilePathView.swift @@ -1,6 +1,8 @@ import SwiftUI struct FilePathView: View { + @AppStorage("accent") var accent = CodableColor(.accentColor) + var folderName: String var fileName: String @@ -9,7 +11,7 @@ struct FilePathView: View { HStack { HStack(spacing: 4) { Image(systemName: "folder.fill") - .foregroundColor(.accentColor) + .foregroundColor(accent.toColor()) Text(folderName) } @@ -18,7 +20,7 @@ struct FilePathView: View { HStack(spacing: 4) { Image(systemName: "doc.fill") - .foregroundColor(.accentColor) + .foregroundColor(accent.toColor()) Text(fileName) .accessibilityIdentifier(Keys.Detail.PathBarFile) } diff --git a/ClassDumper/Views/FileRowView.swift b/ClassDumper/Views/FileRowView.swift index 1e2be1d..3d1c2fa 100644 --- a/ClassDumper/Views/FileRowView.swift +++ b/ClassDumper/Views/FileRowView.swift @@ -24,6 +24,7 @@ struct FileRowRequest: Queryable { struct FileRowView: View { @AppStorage("scopedSearchPreference") var scopedSearchPreference = Preferences.Defaults.scopedSearch + @AppStorage("accent") var accent = CodableColor(.accentColor) // query to fetch all data @Query(FileRowRequest()) @@ -64,7 +65,7 @@ struct FileRowView: View { } icon: { Image(systemName: "doc") .symbolVariant(.fill) - .foregroundColor(.accentColor) + .foregroundColor(accent.toColor()) } } .accessibilityIdentifier(Keys.Middle.Row) diff --git a/ClassDumper/Views/FolderRowView.swift b/ClassDumper/Views/FolderRowView.swift index 5edca6e..a079bae 100644 --- a/ClassDumper/Views/FolderRowView.swift +++ b/ClassDumper/Views/FolderRowView.swift @@ -25,6 +25,8 @@ struct FolderRowRequest: Queryable { extension AppView { struct FolderRowView: View { + @AppStorage("accent") var accent = CodableColor(.accentColor) + @Query(FolderRowRequest()) var folderRows: FolderRowResponse @@ -38,7 +40,7 @@ extension AppView { } } icon: { Image(systemName: "folder") - .foregroundColor(.accentColor) + .foregroundColor(accent.toColor()) } .badge(badge) } diff --git a/ClassDumper/Views/InformationStyle.swift b/ClassDumper/Views/InformationStyle.swift index 383afa3..0e1b80e 100644 --- a/ClassDumper/Views/InformationStyle.swift +++ b/ClassDumper/Views/InformationStyle.swift @@ -2,23 +2,27 @@ import SwiftUI /// The style for information text struct InformationStyle: ViewModifier { + @AppStorage("accent") var accent = CodableColor(.accentColor) + func body(content: Content) -> some View { content .frame(maxWidth: .infinity, alignment: .leading) - .foregroundColor(.accentColor) + .foregroundColor(accent.toColor()) .font(.callout) } } /// The style for information boxes struct InformationBox: ViewModifier { + @AppStorage("accent") var accent = CodableColor(.accentColor) + func body(content: Content) -> some View { content .padding() .frame(maxWidth: .infinity, alignment: .center) - .background(Color.accentColor.opacity(0.07)) + .background(accent.toColor().opacity(0.07)) .buttonStyle(.borderedProminent) - .tint(.accentColor) + .tint(accent.toColor()) .cornerRadius(10) .padding() } diff --git a/ClassDumper/Views/Settings/DebugTabView.swift b/ClassDumper/Views/Settings/DebugTabView.swift index 3631b8a..d5025b7 100644 --- a/ClassDumper/Views/Settings/DebugTabView.swift +++ b/ClassDumper/Views/Settings/DebugTabView.swift @@ -3,6 +3,7 @@ import SwiftUI struct DebugSettingsView: View { @AppStorage("enableVerboseImportErrorLogging") var enableVerboseImportErrorLogging = Preferences.Defaults.verboseErrors @AppStorage("dialogLengthImportErrorLogging") var dialogLengthImportErrorLogging = Preferences.Defaults.dialogLength + @AppStorage("accent") var accent = CodableColor(.accentColor) let helpLogging = """ This setting could be useful if you need to see the original message. @@ -51,6 +52,7 @@ Note that verbose error dialogs will disable this setting. } .help(helpErrorLength) } + .tint(accent.toColor()) .modifier(PreferencesTabViewModifier(sectionTitle: "Errors")) } } diff --git a/ClassDumper/Views/Settings/GeneralTabView.swift b/ClassDumper/Views/Settings/GeneralTabView.swift index 198b68e..3e09b55 100644 --- a/ClassDumper/Views/Settings/GeneralTabView.swift +++ b/ClassDumper/Views/Settings/GeneralTabView.swift @@ -2,11 +2,13 @@ import SwiftUI import CodeEditor struct GeneralSettingsView: View { + @AppStorage("accent") var accent = CodableColor(.accentColor) @AppStorage("codeViewerTheme") var theme: CodeEditor.ThemeName = Preferences.Defaults.themeName @AppStorage("codeViewerFontSize") var fontSize: Int = Preferences.Defaults.fontSize var body: some View { VStack(alignment: .leading, spacing: 20) { + AccentColor() ThemePicker() FontSizePicker() ResetDataButton() @@ -16,6 +18,43 @@ struct GeneralSettingsView: View { extension GeneralSettingsView { + @ViewBuilder + func AccentColor() -> some View { + LazyHGrid(rows: [GridItem(.flexible(minimum: 30, maximum: .infinity))], alignment: .top, spacing: 1) { + ForEach(accents) { option in + Button { + accent = option.color + } label: { + VStack { + Circle() + .fill(option.color.toColor()) + .frame(width: 15, height: 15) + .padding(5) + .accessibilityIdentifier("\(Keys.Settings.AccentColorButton)-\(option.name)") + .overlay(content: { + if accent == option.color { + Circle() + .fill(.white) + .frame(width: 6, height: 6, alignment: .center) + .accessibilityLabel("Selected accent color") + } + }) + + Text(accent == option.color ? option.name : "") + .frame(minHeight: 5) + .fixedSize(horizontal: true, vertical: false) + .frame(width: 35) + .focusable(false) + .accessibilityLabel(option.name) + } + .tag(option.id) + } + .buttonStyle(.plain) + } + } + .modifier(PreferencesTabViewModifier(sectionTitle: "Accent color")) + } + @ViewBuilder func ThemePicker() -> some View { Picker("", selection: $theme) { @@ -27,6 +66,7 @@ extension GeneralSettingsView { .labelsHidden() .pickerStyle(.menu) .fixedSize() + .tint(accent.toColor()) .modifier(PreferencesTabViewModifier(sectionTitle: "Code theme")) } @@ -40,6 +80,7 @@ extension GeneralSettingsView { .frame(minWidth: 55, maxWidth: 85) .fixedSize() } + .tint(accent.toColor()) .modifier(PreferencesTabViewModifier(sectionTitle: "Font size")) } diff --git a/ClassDumper/Views/Settings/SettingsView.swift b/ClassDumper/Views/Settings/SettingsView.swift index 989fa52..4205b25 100644 --- a/ClassDumper/Views/Settings/SettingsView.swift +++ b/ClassDumper/Views/Settings/SettingsView.swift @@ -7,6 +7,8 @@ struct SettingsView: View { } var body: some View { + @AppStorage("accent") var accent = CodableColor(.accentColor) + TabView { GeneralSettingsView() .tabItem { @@ -20,6 +22,7 @@ struct SettingsView: View { } .tag(Tabs.debug) } + .tint(accent.toColor()) .padding(20) .frame(width: 500, alignment: .leading) } diff --git a/ClassDumperUITests/ClassDumperUITests.swift b/ClassDumperUITests/ClassDumperUITests.swift index a612f1a..05a9dab 100644 --- a/ClassDumperUITests/ClassDumperUITests.swift +++ b/ClassDumperUITests/ClassDumperUITests.swift @@ -36,5 +36,6 @@ final class ClassDumperUITests: UITestCase { .tapFirst(.filterToggle, containing: "Show selected") // TODO: CI is failing this test although it is working locally // .selectPopupButton("Show all") + .checkAccentColorTappable() } } diff --git a/ClassDumperUITests/ImportFlow.swift b/ClassDumperUITests/ImportFlow.swift index ff65268..ec2a881 100644 --- a/ClassDumperUITests/ImportFlow.swift +++ b/ClassDumperUITests/ImportFlow.swift @@ -81,6 +81,7 @@ struct ImportFlow: Screen { } } + @discardableResult func check(_ element: Component, exists: Bool) -> Self { let forElement = getComponent(for: element) @@ -126,6 +127,19 @@ struct ImportFlow: Screen { return self } + + @discardableResult + func checkAccentColorTappable() -> Self { + open(.settings) + + app.windows.buttons["General"].tap() + + let blueAccentButton = app.buttons["\(Keys.Settings.AccentColorButton)-Blue"] + XCTAssert(blueAccentButton.exists) + blueAccentButton.tap() + + return self + } fileprivate func open(_ shortcut: Shortcut) { app.typeKey(shortcut.rawValue, modifierFlags: .command)