Update UI.

This commit is contained in:
Max Goedjen 2025-09-14 01:14:40 -07:00
parent 98e21ab449
commit c9096ba915
No known key found for this signature in database
5 changed files with 79 additions and 82 deletions

View File

@ -2,7 +2,7 @@ import Foundation
import SwiftUI
/// A release is a representation of a downloadable update.
public struct Release: Codable, Sendable {
public struct Release: Codable, Sendable, Hashable {
/// The user-facing name of the release. Typically "Secretive 1.2.3"
public let name: String
@ -62,9 +62,9 @@ fileprivate extension AttributedString {
return AttributedString("\n") + string
.transformingAttributes(\.font) { font in
font.value = switch level {
case 2: .title3.bold()
case 3: .title3
default: .body
case 2: .headline.bold()
case 3: .headline
default: .subheadline
}
}
.transformingAttributes(\.underlineStyle) { underline in

View File

@ -24,7 +24,7 @@ extension View {
}
struct MenuButtonModifier: ViewModifier {
struct ToolbarCircleButtonModifier: ViewModifier {
func body(content: Content) -> some View {
if #available(macOS 26.0, *) {
@ -40,8 +40,8 @@ struct MenuButtonModifier: ViewModifier {
extension View {
func menuButton() -> some View {
modifier(MenuButtonModifier())
func toolbarCircleButton() -> some View {
modifier(ToolbarCircleButtonModifier())
}
}

View File

@ -1,6 +1,6 @@
import SwiftUI
struct ToolbarButtonStyle: ButtonStyle {
struct ToolbarStatusButtonStyle: ButtonStyle {
private let lightColor: Color
private let darkColor: Color
@ -56,3 +56,24 @@ struct ToolbarButtonStyle: ButtonStyle {
}
}
}
struct ToolbarButtonStyle: ButtonStyle {
var tint: Color = .white.opacity(0.1)
func makeBody(configuration: Configuration) -> some View {
if #available(macOS 26.0, *) {
configuration
.label
.padding(10)
.glassEffect(.regular.interactive().tint(tint))
} else {
configuration
.label
.padding(EdgeInsets(top: 6, leading: 8, bottom: 6, trailing: 8))
.foregroundColor(.white)
.clipShape(RoundedRectangle(cornerRadius: 5))
}
}
}

View File

@ -6,19 +6,21 @@ import Brief
struct ContentView: View {
@AppStorage("defaultsHasRunSetup") var hasRunSetup = false
@State var showingCreation = false
@State var runningSetup = false
@State var showingAgentInfo = false
@State var activeSecret: AnySecret?
@Environment(\.colorScheme) var colorScheme
@Environment(\.secretStoreList) private var storeList
@Environment(\.updater) private var updater: any UpdaterProtocol
@Environment(\.agentStatusChecker) private var agentStatusChecker: any AgentStatusCheckerProtocol
@State private var selectedUpdate: Release?
@Environment(\.colorScheme) private var colorScheme
@Environment(\.openWindow) private var openWindow
@Environment(\.secretStoreList) private var storeList
@Environment(\.updater) private var updater
@Environment(\.agentStatusChecker) private var agentStatusChecker
@AppStorage("defaultsHasRunSetup") private var hasRunSetup = false
@State private var showingCreation = false
@State private var showingAppPathNotice = false
@State private var runningSetup = false
@State private var showingAgentInfo = false
var body: some View {
VStack {
@ -102,24 +104,9 @@ extension ContentView {
.font(.headline)
.foregroundColor(.white)
})
.buttonStyle(ToolbarButtonStyle(color: color))
.buttonStyle(ToolbarStatusButtonStyle(color: color))
.sheet(item: $selectedUpdate) { update in
VStack {
if updater.currentVersion.isTestBuild {
VStack {
if let description = updater.currentVersion.previewDescription {
Text(description)
}
Link(destination: URL(string: "https://github.com/maxgoedjen/secretive/actions/workflows/nightly.yml")!) {
Button(.updaterDownloadLatestNightlyButton) {}
.frame(maxWidth: .infinity)
.primaryButton()
}
}
.padding()
}
UpdateDetailView(update: update)
}
UpdateDetailView(update: update)
}
}
}
@ -130,7 +117,7 @@ extension ContentView {
Button(.appMenuNewSecretButton, systemImage: "plus") {
showingCreation = true
}
.menuButton()
.toolbarCircleButton()
}
}
@ -157,7 +144,7 @@ extension ContentView {
}
})
.buttonStyle(
ToolbarButtonStyle(
ToolbarStatusButtonStyle(
lightColor: agentStatusChecker.running ? .black.opacity(0.05) : .red.opacity(0.75),
darkColor: agentStatusChecker.running ? .white.opacity(0.05) : .red.opacity(0.5),
)
@ -179,7 +166,7 @@ extension ContentView {
.font(.headline)
.foregroundColor(.white)
})
.buttonStyle(ToolbarButtonStyle(color: .orange))
.buttonStyle(ToolbarStatusButtonStyle(color: .orange))
.popover(isPresented: $showingAppPathNotice, attachmentAnchor: attachmentAnchor, arrowEdge: .bottom) {
VStack {
Image(systemName: "exclamationmark.triangle")

View File

@ -3,61 +3,50 @@ import Brief
struct UpdateDetailView: View {
@Environment(\.updater) var updater: any UpdaterProtocol
@Environment(\.updater) var updater
@Environment(\.openURL) var openURL
let update: Release
var body: some View {
VStack {
Text(.updateVersionName(updateName: update.name)).font(.title)
GroupBox(label: Text(.updateReleaseNotesTitle)) {
ScrollView {
attributedBody
}
}
HStack {
if !update.critical {
Button(.updateIgnoreButton) {
Task {
await updater.ignore(release: update)
VStack(spacing: 0) {
HStack {
if !update.critical {
Button(.updateIgnoreButton) {
Task {
await updater.ignore(release: update)
}
}
.buttonStyle(ToolbarButtonStyle())
}
Spacer()
if updater.currentVersion.isTestBuild {
Button(.updaterDownloadLatestNightlyButton) {
openURL(URL(string: "https://github.com/maxgoedjen/secretive/actions/workflows/nightly.yml")!)
}
.buttonStyle(ToolbarButtonStyle(tint: .accentColor))
}
Button(.updateUpdateButton) {
openURL(update.html_url)
}
.buttonStyle(ToolbarButtonStyle(tint: .accentColor))
.keyboardShortcut(.defaultAction)
}
Button(.updateUpdateButton) {
NSWorkspace.shared.open(update.html_url)
.padding()
Divider()
Form {
Section {
Text(update.attributedBody)
} header: {
Text(.updateVersionName(updateName: update.name)) .headerProminence(.increased)
}
}
.keyboardShortcut(.defaultAction)
}
.formStyle(.grouped)
}
.padding()
.frame(maxWidth: 500)
}
var attributedBody: Text {
var text = Text(verbatim: "")
for line in update.body.split(whereSeparator: \.isNewline) {
let attributed: Text
let split = line.split(separator: " ")
let unprefixed = split.dropFirst().joined(separator: " ")
if let prefix = split.first {
switch prefix {
case "#":
attributed = Text(unprefixed).font(.title) + Text(verbatim: "\n")
case "##":
attributed = Text(unprefixed).font(.title2) + Text(verbatim: "\n")
case "###":
attributed = Text(unprefixed).font(.title3) + Text(verbatim: "\n")
default:
attributed = Text(line) + Text(verbatim: "\n\n")
}
} else {
attributed = Text(line) + Text(verbatim: "\n\n")
}
text = text + attributed
}
return text
}
}
#Preview {
UpdateDetailView(update: .init(name: "3.0.0", prerelease: false, html_url: URL(string: "https://example.com")!, body: "Hello"))
}