mirror of
https://github.com/yattee/yattee.git
synced 2026-07-20 14:22:02 +00:00
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
160 lines
4.8 KiB
Swift
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)
|
|
}
|