diff --git a/phpmon/Domain/SwiftUI/Common/Markdown/MarkdownTextView.swift b/phpmon/Domain/SwiftUI/Common/Markdown/MarkdownTextView.swift index e0ce9709..46abc266 100644 --- a/phpmon/Domain/SwiftUI/Common/Markdown/MarkdownTextView.swift +++ b/phpmon/Domain/SwiftUI/Common/Markdown/MarkdownTextView.swift @@ -11,14 +11,16 @@ import SwiftUI struct MarkdownTextView: View { let string: String let fontSize: CGFloat + let textColor: NSColor - init(_ string: String, fontSize: CGFloat = 12) { + init(_ string: String, fontSize: CGFloat = 12, textColor: NSColor = .labelColor) { self.string = string self.fontSize = fontSize + self.textColor = textColor } var body: some View { - MarkdownTextViewRepresentable(string: string, fontSize: fontSize) + MarkdownTextViewRepresentable(string: string, fontSize: fontSize, textColor: textColor) } } diff --git a/phpmon/Domain/SwiftUI/Common/Markdown/MarkdownTextViewRepresentable.swift b/phpmon/Domain/SwiftUI/Common/Markdown/MarkdownTextViewRepresentable.swift index 13e0c7cd..e438c32e 100644 --- a/phpmon/Domain/SwiftUI/Common/Markdown/MarkdownTextViewRepresentable.swift +++ b/phpmon/Domain/SwiftUI/Common/Markdown/MarkdownTextViewRepresentable.swift @@ -13,6 +13,7 @@ import AppKit struct MarkdownTextViewRepresentable: NSViewRepresentable { let string: String let fontSize: CGFloat + let textColor: NSColor // MARK: - Static Properties @@ -44,14 +45,15 @@ struct MarkdownTextViewRepresentable: NSViewRepresentable { func updateNSView(_ textView: CodeBlockTextView, context: Context) { let coordinator = context.coordinator - guard string != coordinator.lastString || fontSize != coordinator.lastFontSize else { return } + guard string != coordinator.lastString || fontSize != coordinator.lastFontSize || textColor != coordinator.lastTextColor else { return } configure(textView, coordinator: coordinator) } private func configure(_ textView: CodeBlockTextView, coordinator: Coordinator) { coordinator.lastString = string coordinator.lastFontSize = fontSize - let attributed = Self.buildAttributedString(from: string, fontSize: fontSize) + coordinator.lastTextColor = textColor + let attributed = Self.buildAttributedString(from: string, fontSize: fontSize, textColor: textColor) textView.textStorage?.setAttributedString(attributed) textView.invalidateIntrinsicContentSize() } @@ -59,11 +61,12 @@ struct MarkdownTextViewRepresentable: NSViewRepresentable { class Coordinator { var lastString: String? var lastFontSize: CGFloat? + var lastTextColor: NSColor? } // MARK: - Attributed String Builder - static func buildAttributedString(from string: String, fontSize: CGFloat) -> NSAttributedString { + static func buildAttributedString(from string: String, fontSize: CGFloat, textColor: NSColor = .labelColor) -> NSAttributedString { let result = NSMutableAttributedString() let font = NSFont.systemFont(ofSize: fontSize) @@ -73,7 +76,7 @@ struct MarkdownTextViewRepresentable: NSViewRepresentable { let defaultAttributes: [NSAttributedString.Key: Any] = [ .font: font, - .foregroundColor: NSColor.labelColor, + .foregroundColor: textColor, .paragraphStyle: paragraphStyle ] @@ -85,8 +88,8 @@ struct MarkdownTextViewRepresentable: NSViewRepresentable { // Collect code span ranges once for bold and italic passes let codeRanges = codeSpanRanges(in: result) - handleBoldMarkup(in: result, fontSize: fontSize, paragraphStyle: paragraphStyle, codeRanges: codeRanges) - handleItalicMarkup(in: result, fontSize: fontSize, paragraphStyle: paragraphStyle, codeRanges: codeRanges) + handleBoldMarkup(in: result, fontSize: fontSize, paragraphStyle: paragraphStyle, textColor: textColor, codeRanges: codeRanges) + handleItalicMarkup(in: result, fontSize: fontSize, paragraphStyle: paragraphStyle, textColor: textColor, codeRanges: codeRanges) return result } @@ -156,6 +159,7 @@ struct MarkdownTextViewRepresentable: NSViewRepresentable { in result: NSMutableAttributedString, fontSize: CGFloat, paragraphStyle: NSParagraphStyle, + textColor: NSColor, codeRanges: [NSRange] ) { let boldFont = NSFont.boldSystemFont(ofSize: fontSize) @@ -168,7 +172,7 @@ struct MarkdownTextViewRepresentable: NSViewRepresentable { string: innerText, attributes: [ .font: boldFont, - .foregroundColor: NSColor.labelColor, + .foregroundColor: textColor, .paragraphStyle: paragraphStyle ] ) @@ -182,6 +186,7 @@ struct MarkdownTextViewRepresentable: NSViewRepresentable { in result: NSMutableAttributedString, fontSize: CGFloat, paragraphStyle: NSParagraphStyle, + textColor: NSColor, codeRanges: [NSRange] ) { let italicFont = NSFontManager.shared.convert( @@ -197,7 +202,7 @@ struct MarkdownTextViewRepresentable: NSViewRepresentable { string: innerText, attributes: [ .font: italicFont, - .foregroundColor: NSColor.labelColor, + .foregroundColor: textColor, .paragraphStyle: paragraphStyle ] ) diff --git a/phpmon/Domain/SwiftUI/Startup/StartupAlertHeaderView.swift b/phpmon/Domain/SwiftUI/Startup/StartupAlertHeaderView.swift index a2806da0..e1e9533a 100644 --- a/phpmon/Domain/SwiftUI/Startup/StartupAlertHeaderView.swift +++ b/phpmon/Domain/SwiftUI/Startup/StartupAlertHeaderView.swift @@ -13,24 +13,15 @@ struct StartupAlertHeaderView: View { let subtitleText: String var body: some View { - HStack(spacing: 12) { - Image(nsImage: NSApp.applicationIconImage) - .resizable() - .frame(width: 60, height: 60) + VStack(alignment: .leading, spacing: 5) { + Text(titleText) + .font(.system(size: 15, weight: .bold, design: .rounded)) + .textSelection(.enabled) + .padding(.bottom, 2) - VStack(alignment: .leading, spacing: 5) { - Text(titleText) - .font(.system(size: 15, weight: .bold)) - .textSelection(.enabled) - - MarkdownTextView(subtitleText, fontSize: 13) - .fixedSize(horizontal: false, vertical: true) - } - - Spacer() + MarkdownTextView(subtitleText, fontSize: 12) + .fixedSize(horizontal: false, vertical: true) } - .padding(15) - .padding(.top, 0) } } diff --git a/phpmon/Domain/SwiftUI/Startup/StartupAlertView.swift b/phpmon/Domain/SwiftUI/Startup/StartupAlertView.swift index f98814df..1de5faa7 100644 --- a/phpmon/Domain/SwiftUI/Startup/StartupAlertView.swift +++ b/phpmon/Domain/SwiftUI/Startup/StartupAlertView.swift @@ -13,39 +13,48 @@ struct StartupAlertView: View { var body: some View { VStack(spacing: 0) { - StartupAlertHeaderView( - titleText: viewModel.check.titleText, - subtitleText: viewModel.check.subtitleText - ) + HStack(alignment: .top, spacing: 12) { + Image(nsImage: NSApp.applicationIconImage) + .resizable() + .frame(width: 60, height: 60) - // Fix command description: only shown in idle state when a fix is available - if viewModel.state == .idle && viewModel.hasFix { - StartupFixCommandView( - command: viewModel.check.fixDescription ?? "" - ) - .padding(.horizontal, 10).padding(.leading, 72) + VStack(alignment: .leading, spacing: 10) { + StartupAlertHeaderView( + titleText: viewModel.check.titleText, + subtitleText: viewModel.check.subtitleText + ) + .padding(.top, 5) + .padding(.bottom, 8) + + // Fix command description: only shown in idle state when a fix is available + if viewModel.state == .idle && viewModel.hasFix { + StartupFixCommandView( + command: viewModel.check.fixDescription ?? "" + ) + } else if viewModel.state == .idle && !viewModel.hasFix { + Divider() + } + + // Terminal output: shown during and after fix execution + if !viewModel.outputLines.isEmpty + && (viewModel.state == .running || viewModel.state == .completed || viewModel.state == .failed) { + StartupOutputView( + lines: viewModel.outputLines, + isRunning: viewModel.state == .running + ) + } + + // Description text: shown in idle state + if !viewModel.check.descriptionText.isEmpty && viewModel.state == .idle { + MarkdownTextView(viewModel.check.descriptionText, fontSize: 12, textColor: NSColor.secondaryLabelColor) + .padding(.vertical, 5) + } + } + .padding(.trailing, 10) .frame(maxWidth: .infinity, alignment: .leading) } - - // Terminal output: shown during and after fix execution - if !viewModel.outputLines.isEmpty - && (viewModel.state == .running || viewModel.state == .completed || viewModel.state == .failed) { - StartupOutputView( - lines: viewModel.outputLines, - isRunning: viewModel.state == .running - ) - .padding(15).padding(.leading, 72) - .frame(maxWidth: .infinity, alignment: .leading) - } - - // Description text: shown in idle state - if !viewModel.check.descriptionText.isEmpty && viewModel.state == .idle { - MarkdownTextView(viewModel.check.descriptionText, fontSize: 12) - .padding(.vertical, 15) - .padding(.horizontal, 20) - .padding(.leading, 64) - .frame(maxWidth: .infinity, alignment: .leading) - } + .padding(15) + .padding(.top, -5) Divider() @@ -133,7 +142,7 @@ struct StartupAlertView: View { OutputLine(text: "==> Linking Binary 'php' to '/opt/homebrew/bin/php'", stream: .stdOut), OutputLine(text: "Warning: php is keg-only and must be linked with --force", stream: .stdErr), OutputLine(text: "", stream: .stdOut), - OutputLine(text: "---\nFix did not resolve the issue.", stream: .stdOut) + OutputLine(text: "\nFix did not resolve the issue.", stream: .stdOut) ] )) } @@ -155,7 +164,7 @@ struct StartupAlertView: View { outputLines: [ OutputLine(text: "==> Linking Binary 'php' to '/opt/homebrew/bin/php'", stream: .stdOut), OutputLine(text: "==> Linking php... linked 25 files", stream: .stdOut), - OutputLine(text: "---\nFix applied successfully! Continuing...", stream: .stdOut) + OutputLine(text: "\nFix applied successfully! Continuing...", stream: .stdOut) ] )) } diff --git a/phpmon/Domain/SwiftUI/Startup/StartupAlertViewModel.swift b/phpmon/Domain/SwiftUI/Startup/StartupAlertViewModel.swift index e3f54309..494ff92e 100644 --- a/phpmon/Domain/SwiftUI/Startup/StartupAlertViewModel.swift +++ b/phpmon/Domain/SwiftUI/Startup/StartupAlertViewModel.swift @@ -101,19 +101,19 @@ class StartupAlertViewModel: ObservableObject { /** Marks the current fix as completed, with success. */ @MainActor private func pass() { self.state = .completed - self.appendOutput("---\n\("startup.fix.applied".localized)", .stdOut) + self.appendOutput("\n\("startup.fix.applied".localized)", .stdOut) } /** Marks the current fix as completed, with failure. */ @MainActor private func fail() { self.state = .failed - self.appendOutput("---\n\("startup.fix.not_resolved".localized)", .stdErr) + self.appendOutput("\n\("startup.fix.not_resolved".localized)", .stdErr) } /** An error occurred. */ @MainActor private func errorAndIdle(_ error: Error) { self.state = .failed - self.appendOutput("---\nError: \(error.localizedDescription)", .stdErr) + self.appendOutput("\nError: \(error.localizedDescription)", .stdErr) } // MARK: - Alert Outcomes