Files
yattee/Yattee/Views/Settings/ContributorsView.swift
Arkadiusz Fal 9e2b0fa095 Extend opaque background to settings and other pushed pages on macOS
The NavigationStack-level background wasn't enough: macOS draws its own
translucent material behind pushed navigation pages, and Form/List
containers draw a translucent scroll background on top of anything
placed behind them, so several pages kept the wallpaper-tinted look.

Bake the opaque background into SettingsFormContainer (covers all pages
built on it, pushed or not) and add opaqueSettingsFormBackground(),
which hides the container's scroll background before applying the
opaque one, to every macOS-reachable Form/List settings page: player
controls and its sub-editors, sidebar settings, edit source, legacy
data import, subscription/playlist import, customize home and its
shortcut style page, and the log viewer. Contributors, translators and
the remote control device page get the plain opaque background.

Claude-Session: https://claude.ai/code/session_0154KH8RAVAvm6iVanhmoW8o
2026-07-07 22:16:09 +02:00

160 lines
4.8 KiB
Swift

//
// ContributorsView.swift
// Yattee
//
// Displays GitHub contributors for the Yattee repository.
//
import NukeUI
import SwiftUI
struct ContributorsView: View {
@Environment(\.openURL) private var openURL
@Environment(\.appEnvironment) private var appEnvironment
@State private var contributors: [GitHubContributor] = []
@State private var isLoading = false
@State private var errorMessage: String?
var body: some View {
content
.opaqueWindowBackground()
.navigationTitle(String(localized: "settings.contributors.title"))
#if os(iOS)
.navigationBarTitleDisplayMode(.inline)
#endif
.task {
await loadContributors()
}
}
// MARK: - Content
@ViewBuilder
private var content: some View {
if isLoading && contributors.isEmpty {
ProgressView()
.frame(maxWidth: .infinity, maxHeight: .infinity)
} else if let error = errorMessage, contributors.isEmpty {
ContentUnavailableView {
Label(String(localized: "common.error"), systemImage: "exclamationmark.triangle")
} description: {
Text(error)
} actions: {
Button(String(localized: "common.retry")) {
Task {
await loadContributors()
}
}
.buttonStyle(.borderedProminent)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
} else {
contributorsList
}
}
private var contributorsList: some View {
SettingsFormContainer {
SettingsFormSection(footer: "settings.contributors.section.footer") {
ForEach(contributors) { contributor in
contributorRow(contributor)
}
}
}
}
private func contributorRow(_ contributor: GitHubContributor) -> some View {
Button {
if let url = contributor.profileURL {
openURL(url)
}
} label: {
HStack(spacing: 12) {
// Avatar
LazyImage(url: contributor.avatarURL) { state in
ZStack {
Circle()
.fill(.quaternary)
.overlay {
if state.image == nil {
Text(String(contributor.login.prefix(1).uppercased()))
.font(.subheadline)
.fontWeight(.medium)
.foregroundStyle(.secondary)
}
}
if let image = state.image {
image
.resizable()
.aspectRatio(contentMode: .fill)
}
}
}
.frame(width: 40, height: 40)
.clipShape(Circle())
// Username and commits
VStack(alignment: .leading, spacing: 2) {
Text(contributor.login)
.font(.body)
.fontWeight(.semibold)
Text(String(localized: "settings.contributors.commits \(contributor.contributions)"))
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
Image(systemName: "arrow.up.right")
.foregroundStyle(.secondary)
}
.contentShape(Rectangle())
}
#if os(macOS)
.buttonStyle(.plain)
#endif
}
// MARK: - Data Loading
private func loadContributors() async {
guard let appEnvironment else { return }
let api = GitHubAPI(httpClient: appEnvironment.httpClient)
isLoading = true
errorMessage = nil
do {
let result = try await api.contributors()
await MainActor.run {
contributors = result
isLoading = false
}
} catch let error as APIError {
await MainActor.run {
if case .rateLimited = error {
errorMessage = String(localized: "settings.contributors.error.rateLimited")
} else {
errorMessage = error.localizedDescription
}
isLoading = false
}
} catch {
await MainActor.run {
errorMessage = error.localizedDescription
isLoading = false
}
}
}
}
#Preview {
NavigationStack {
ContributorsView()
}
.appEnvironment(.preview)
}