CxLLM-IOS/CxLLMStudio/Views/Git/GitView.swift
2026-05-16 10:52:04 -05:00

190 lines
7.8 KiB
Swift

// CxLLM Studio iOS GitView.swift
// Gitea repository dashboard adapted for iOS
import SwiftUI
import CxGit
struct GitView: View {
@Environment(GitService.self) private var git
enum Section: String, CaseIterable {
case repos = "Repos"
case pulls = "PRs"
case issues = "Issues"
}
@State private var section: Section = .repos
@State private var selectedRepo: GitRepository?
var body: some View {
VStack(spacing: 0) {
// Connection bar
HStack(spacing: 8) {
Circle().fill(git.isConnected ? .green : .red).frame(width: 8, height: 8)
Text(git.isConnected ? "Connected" : "Disconnected").font(.caption)
Spacer()
if let user = git.currentUser {
Label(user.login, systemImage: "person.circle").font(.caption).foregroundStyle(.secondary)
}
Button { Task { await git.refreshAll() } } label: {
Image(systemName: "arrow.clockwise").font(.caption)
}
}
.padding(.horizontal).padding(.vertical, 8)
.background(.ultraThinMaterial)
// Section picker
Picker("Section", selection: $section) {
ForEach(Section.allCases, id: \.self) { s in
Text(s.rawValue).tag(s)
}
}
.pickerStyle(.segmented)
.padding(.horizontal).padding(.vertical, 8)
// Content
switch section {
case .repos: reposList
case .pulls: pullsList
case .issues: issuesList
}
}
.task {
await git.refreshAll()
}
}
// MARK: - Repos
private var reposList: some View {
Group {
if git.repositories.isEmpty {
ContentUnavailableView("No Repositories", systemImage: "folder",
description: Text("Check your Gitea connection"))
} else {
List(git.repositories) { repo in
NavigationLink {
repoDetail(repo)
} label: {
HStack(spacing: 8) {
Image(systemName: repo.private ? "lock.fill" : "globe")
.foregroundStyle(repo.private ? .orange : .green)
.font(.caption)
VStack(alignment: .leading, spacing: 2) {
Text(repo.name).font(.subheadline.weight(.medium))
if let desc = repo.description, !desc.isEmpty {
Text(desc).font(.caption).foregroundStyle(.secondary).lineLimit(1)
}
}
Spacer()
HStack(spacing: 6) {
Label("\(repo.starsCount)", systemImage: "star").font(.caption2)
Label("\(repo.openIssuesCount)", systemImage: "exclamationmark.circle").font(.caption2)
}.foregroundStyle(.secondary)
}
}
}
}
}
}
private func repoDetail(_ repo: GitRepository) -> some View {
List {
SwiftUI.Section("Info") {
LabeledContent("Name", value: repo.name)
LabeledContent("Default Branch", value: repo.defaultBranch)
LabeledContent("Stars", value: "\(repo.starsCount)")
LabeledContent("Forks", value: "\(repo.forksCount)")
LabeledContent("Open Issues", value: "\(repo.openIssuesCount)")
if let desc = repo.description { LabeledContent("Description", value: desc) }
}
SwiftUI.Section {
Button("Load PRs") {
let parts = repo.fullName.components(separatedBy: "/")
if parts.count == 2 {
Task { await git.loadPullRequests(owner: parts[0], repo: parts[1]); section = .pulls }
}
}
Button("Load Issues") {
let parts = repo.fullName.components(separatedBy: "/")
if parts.count == 2 {
Task { await git.loadIssues(owner: parts[0], repo: parts[1]); section = .issues }
}
}
Button("Load Branches") {
let parts = repo.fullName.components(separatedBy: "/")
if parts.count == 2 {
Task { await git.loadBranches(owner: parts[0], repo: parts[1]) }
}
}
}
}
.navigationTitle(repo.name)
}
// MARK: - Pull Requests
private var pullsList: some View {
Group {
if git.pullRequests.isEmpty {
ContentUnavailableView("No Pull Requests", systemImage: "arrow.triangle.pull",
description: Text("Select a repo to load PRs"))
} else {
List(git.pullRequests) { pr in
HStack(spacing: 8) {
Image(systemName: pr.state == "open" ? "arrow.triangle.pull" : "checkmark.circle.fill")
.foregroundStyle(pr.state == "open" ? .green : .purple)
VStack(alignment: .leading, spacing: 2) {
Text("#\(pr.number) \(pr.title)").font(.subheadline.weight(.medium))
HStack(spacing: 6) {
if let user = pr.user { Text(user.login).font(.caption2) }
if let head = pr.head, let base = pr.base {
Text("\(head.ref)\(base.ref)").font(.caption2).foregroundStyle(.secondary)
}
}
}
Spacer()
Text(pr.state).font(.caption2)
.padding(.horizontal, 6).padding(.vertical, 2)
.background(pr.state == "open" ? Color.green.opacity(0.1) : Color.purple.opacity(0.1))
.clipShape(Capsule())
}
}
}
}
}
// MARK: - Issues
private var issuesList: some View {
Group {
if git.issues.isEmpty {
ContentUnavailableView("No Issues", systemImage: "exclamationmark.circle",
description: Text("Select a repo to load issues"))
} else {
List(git.issues) { issue in
HStack(spacing: 8) {
Image(systemName: issue.state == "open" ? "circle" : "checkmark.circle.fill")
.foregroundStyle(issue.state == "open" ? .green : .secondary)
VStack(alignment: .leading, spacing: 2) {
Text("#\(issue.number) \(issue.title)").font(.subheadline.weight(.medium))
HStack(spacing: 4) {
if let user = issue.user { Text(user.login).font(.caption2).foregroundStyle(.secondary) }
if let labels = issue.labels {
ForEach(labels.prefix(3)) { label in
Text(label.name).font(.system(size: 9))
.padding(.horizontal, 4).padding(.vertical, 1)
.background(Color.secondary.opacity(0.1)).clipShape(Capsule())
}
}
}
}
Spacer()
}
}
}
}
}
}